iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0
Software Development

全端工程師團隊的養成計畫系列 第 29

Day29 從傳統post 到現在前後端分離,前端思維的改變

  • 分享至 

  • xImage
  •  

在實作了多項功能後,接下來想聊聊前後端分離所帶來的改變。
由於過去的經驗多半是以 MVC (Razor) 為主,因此這裡將以 MVC 模式 與本案的 前後端分離 (SPA) 模式 進行比較。

我們會以本案的實際需求作為範例,透過「查詢」與「編輯」這兩個需求,並以下方兩個 User Story 作為開發案例(圖29-1),來分析設計思維上的差異。那麼,當開發者接到這個任務時,會如何著手呢?

  • 身為一個管理者,我需要知道目前納管的授權軟體清單。
  • 身為一個管理者,我需要異動指定的授權軟體資清單資料,可以成功儲存。
    圖29-1:查詢軟體清單後,使用者可以進一步進行資料編輯。
    圖29-1

MVC 會怎麼設計

  1. 功能拆分為兩個獨立的 MVC 模組:
    • 查詢授權軟體清單
    • 編輯軟體資訊
  2. 功能間的關聯性:
    • 使用者先透過查詢功能找到目標軟體
    • 點選欲編輯的資料後,系統會跳轉至編輯頁面
    • 查詢頁跳轉至編輯頁時,需攜帶該筆軟體的 ID 以便載入正確的資料
  3. 查詢授權軟體清單功能
    • Controller 控制查詢條件,並透過 Model 撈取與整合資料
    • 開發查詢用的 ActionResult 回應
    • View 綁定 Model 後,負責繪製查詢結果畫面
    • 提供由 View 跳轉至編輯功能的導頁機制
    • JavaScript 基本防呆與驗證
  4. 編輯軟體資訊功能
    • 承接前一頁傳遞的軟體 ID 參數
    • 根據 ID,透過 Model 撈取資料庫中的軟體明細
    • View 綁定 Model,並繪製編輯用的表單畫面
    • 開發「儲存」的 ActionResult 回應
    • 開發「刪除」的 ActionResult 回應

前後端分離的設計

  1. 功能拆分:
    • 前端查詢授權軟體清單
    • 前端編輯軟體明細資訊
  2. 功能關聯:
    • 使用者於查詢畫面點選「編輯」後,可直接跳出視窗進行資料編輯
  3. 共用 Store 設計:
    • 建立並維護 store.ts
    • 定義 Dto 結構與共用的參數清單
    • 規劃後端對應的 Action
  4. 前端開發:
    • 開發查詢功能的 Vue 元件
    • 開發編輯功能的 Vue 元件
  5. 後端開發:
    • 實作 API,接收來自 store action 的請求並回應資料
  6. 前後端整合測試

兩者差異分析

在設計觀點上,前後端分離 特別重視「兩個功能間如何切換」,這會直接影響到共用 store 的設計方式。
透過直接存取共用的 store,可以實現 部分畫面即時更新,不僅提供更好的使用體驗,也提升了共用元件的彈性。

反觀 傳統 MVC,不同的 View 之間通常僅能依靠 TempDataQuery StringRoute Data 傳遞少量非敏感資料,來完成頁面間的串接。
雖然 MVC 提供了 Layout(整體骨架)與 Partial View(區塊重複利用)的機制,但最終仍是由伺服器產生一份完整的 HTML 回傳。
這種模式下,若僅有部分元素不同,往往需要再製作一份新的 Partial View,邏輯插入與差異化設計的空間有限,也難以進一步抽象成真正可重用的元件。

相較之下,Vue 的 動態渲染 機制可以靈活處理差異,並讓前端以更細緻的方式控制畫面更新,兩者之間仍有顯著差距。

MVC (Razor) vs 前後端分離 (SPA) – 功能設計差異比較

面向 MVC (Razor) 前後端分離 (SPA)
功能拆分 查詢、編輯各自獨立 Controller 與 View 查詢、編輯各自獨立 Vue 元件
功能關聯 查詢 → 導頁至編輯頁 (帶 ID) 查詢 → 點擊後於彈窗直接編輯
資料流 Controller 負責撈資料 + 傳遞至 View 前端透過 store.ts 管理共用 DTO,API 取資料
畫面開發 View 綁定 Model,使用 Razor + HTML 繪製 Vue 元件化開發,UI 與狀態解耦
互動模式 頁面跳轉式,功能邏輯與畫面耦合 單頁互動式,狀態驅動畫面更新
後端設計 ActionResult 處理查詢/編輯/刪除邏輯 提供 API action,單純資料存取與邏輯
前後端整合 前端依賴後端輸出完整 HTML 前端發送 API 請求,後端回傳 JSON
使用體驗 每次操作需換頁,流程較中斷 流暢無刷新體驗,互動性更好

運作原理差異

面向 MVC Partial View Vue 元件 (Component)
渲染流程 Controller 產生 Model → Razor 產生 HTML → 回傳整頁或片段 Component 內部 State/Props → Virtual DOM → 對比更新 → 更新 DOM
資料流 單向:Controller → View → HTML → Browser 雙向 / 單向:Props 從父元件傳入 → Component 內部 State 可雙向綁定 (v-model)
生命週期 只在伺服器端請求時生成 HTML 元件有完整生命周期 (created, mounted, updated, destroyed),可動態更新 DOM
互動更新 若要更新,通常需送表單或再發請求 (Post/Redirect) 可動態更新畫面,無需整頁刷新,事件驅動或響應式自動更新

實際看看動態更新的差異

以「休假申請單」為案例,表單開啟後通常會呈現申請的相關內容,不僅包含休假資訊,還需要顯示簽核歷程。
其中有一個區塊是 簽核流程。假設現在要新增一位簽核人員,如果希望在 點擊新增後只更新簽核清單區塊,而不是重新整個頁面(傳統 form post 的做法),那應該怎麼設計呢?
圖29-2:以請假單表單為範例,其中的簽核資訊在新增簽核人員後,需要能即時刷新更新。
圖29-2

MVC 做法

  1. 透過 ajax-post發送增加簽核的請求。
  2. 回傳的 Data HTML 片段)直接插入到網頁上 ID 為FlowSignList 的元素中完成動態更新內容,完成即時刷新需求。
$.ajax({
      type: "post", //使用 HTTP POST 方法發送請求
      url: getAPFunPath("Flow", "AddFlowSign"),
      data: JSON.stringify({
          "strFlowID": keys[0],
          "strSignID": keys[1],
          "strAddSignUser": addUserID,
      }),
      async: false,
      
      contentType: 'application/json; charset=utf-8',
      success: function (data) {
          if (data.indexOf("alert") > 0) {
              $("#error-div").html(data);
          } else {
              /*
                會將 AJAX 回傳的 data(通常是 HTML 片段)直接插入到網頁上 ID 為 AttachFileList 的元素中達到動態更新內容的標準做法,常用於檔案列表、表格、訊息等即時刷新
              */
              $("#FlowSignList").html(data); // 會將 AJAX 回傳的 data 插入到網頁上 ID 為 AttachFileList 的元素
              alert("新增成功");
          }
         
      },
      error: function (XMLHttpRequest, textStatus, errorThrown) {
          alert("error:AddFlowSign 有誤,請聯繫系統管理員,errorThrown : " + errorThrown);
      }
  });

雖然滿足了部分畫面的即時更新,但仍然有以下缺點:

  • 維護性與可讀性低 :jQuery 程式碼容易隨著功能增加而變得複雜,難以維護,目前範例僅為其中一個CRUD 的需求,通常系統中一個介面完成不同資料的 CRUD 並不少,全數透過 jQuery 達成畫面刷新,程式碼可讀性會大幅降低。
  • jQuery 只操作 DOM,無法自動追蹤資料狀態。每次資料變動都要手動更新畫面,容易漏掉或重複操作。
  • 元件化與重用性低 : 單純以 jQuery 選取器為主,難以拆分成可重用的元件。
  • 狀態管理困難:jQuery 只操作 DOM,無法自動追蹤資料狀態,每次資料變動都要手動更新畫面,容易漏掉或重複操作。

如果是 Vue 會怎麼做?

  1. 新增flowSignStore.ts
    • 定義新增時同步後端的 API addNewUser()
    • 取得回應後將資料回寫至 flowSignList
  2. 流程清單的畫面 vue 元件化
    • 元件畫面需要使用時,import flowSignStore.ts
    • 依需求再指定需要動態更新的事件中調用 flowSignStore.addNewUser
    • 顯示的元件內容雙向綁定 flowSignStore.flowSignList
 //flowSignStore.ts
  import { fetchWrapper , type ApiRspMessage ,type ActionRsp } from '@/utils/helpers/fetch-wrapper'; 
  const  FlowSignList as FlowSignListDto[];
  export const  useflowSignStore= defineStore({
  id: 'flowSignStore',
  state: () => ({
    flowSignList: []  as flowSignListDto[], 
  }),
  actions: {
    async addNewUser(userId: string) {
        try {
         const respData: ActionRsp [] = await fetchWrapper.post(apiURL,userId) as ActionRsp [];
         this.flowSignList = respData;

        }catch (error) {
           if (error instanceof Response) {
             const errorText = await error.text();
             console.error('Failed to create software:', errorText);
           } else {
             console.error('Failed to create software:', error);
           }
      }
    }


});

雖然一開始需要先開發元件或額外建立 Store,但後續就能重複使用,讓共用性最大化。不僅維護性更高、可讀性也大幅提升,更重要的是:透過自動追蹤資料狀態即可觸發畫面更新,無需額外撰寫事件或條件判斷來決定是否要刷新畫面,這點相當省事且值得投入。
以範例來說,當使用者點擊按鈕觸發事件並完成作業後,畫面能自動更新;若沒有這樣的設計,通常就只能依靠 setInterval 等定時機制來達成。

傳統作法 vs Vue 作法

面向 傳統作法 (MVC / jQuery) Vue 作法 (前後端分離 + Store)
畫面更新方式 透過整頁 Postback 或重新載入 HTML 透過 響應式資料綁定,自動更新部分畫面區塊
元件/邏輯共用性 低,常需重複撰寫或複製多份 Partial View 高,可將元件抽離並搭配 Store 重複使用
資料狀態追蹤 需要手動判斷與事件監控(e.g. callback, 判斷 flag) Vue 透過 響應式系統 自動追蹤狀態並刷新畫面
額外處理機制 常需用 setInterval 或手動觸發刷新 無需額外事件,資料變動即自動觸發畫面渲染
維護性 / 可讀性 較低,程式碼分散且重複 較高,結構清晰且能快速定位問題

Ending Remark

總結上述的分析,前後端分離後,開發與設計上的思維有著以下的不同:

  1. 功能切割上面需要考量更細緻,更注重再不同介面(功能)間的介面串接方式,包含共用 Store 與不同 vue 參數 props 傳遞、更細緻如props 傳遞物件結構也需要好好規劃。
  2. 開發會朝向高度共用與元件化:收到開發任務時不是僅考慮單一功能的完成,而去思考有哪些功能可再元件化、哪些可共用的方向開始著手,團隊中開發者,著手前先了解有哪些元件可以使用。
  3. Vue 前後端分離的架構下,前端(Vue)主要負責資料的呈現、使用者互動和介面邏輯,盡量避免處理複雜的業務邏輯、權限判斷、資料整理與重組。後端 API 專心負責負責資料存取、業務邏輯、權限驗證等核心功能。

上一篇
Day28 前後端分離的魔鬼與細節(二)
下一篇
Day30 從築夢到驗收 100%:我們的技術轉型挑戰成果
系列文
全端工程師團隊的養成計畫30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言