日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

前端切换视图_前端架构 101(五):从 Flux 进化到 Model-View-Presenter

發布時間:2023/12/10 HTML 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 前端切换视图_前端架构 101(五):从 Flux 进化到 Model-View-Presenter 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
李熠:前端架構 101(一):在談論它們之前我們需要達成的共識?zhuanlan.zhihu.com李熠:前端架構 101(二): MVC 初探?zhuanlan.zhihu.com李熠:前端架構 101(三):MVC 啟示錄:模塊的職責,作用域和通信?zhuanlan.zhihu.com李熠:前端架構 101(四):MVC 的不足與 Flux 的崛起?zhuanlan.zhihu.com李熠:前端架構 101(六):整潔(Clean Architecture)架構是歸宿?zhuanlan.zhihu.com

在 Flux 架構中,有兩個問題依然沒有被提到,一個是表現層模型,另一個是測試

我們從表現層邏輯說起

表現層模型即 Presenter Model 或者稱之為 View Model。這是一些與業務無關緊要,但是與可視化展示息息相關的數據。簡單的例如某個可折疊的控件是否處于折疊狀態,復雜的可以是某個字段的校驗規則,校驗的出錯信息,或者是圖表的展現類型(餅圖還是柱狀圖)等等。

想象一下在 Flux + React 的框架下這些數據應該存放在哪里?我想包括曾經的我在內的大多數人都會把它放在組件中,這是想當然的事情:既然它們屬于表現層狀態,那么就應該放在表現層的組件中;而不放在 Redux 中的另一個原因是,Redux 并非是所有功能的標配,把所有數據都往 Redux 中集成會讓整個 store 顯得臃腫,維護起來反而不利。

但在實際應用中這些數據并沒有那么純粹,甚至可以說大多數時候表現層模型和業務模型是息息相關,比如用戶允許在下面的表格中選中某些商品,然后選擇將它們的價格清零:

簡易的偽代碼可能是這樣的:

// 每行的選中函數 function onRowSeleted(rowId) {selectedRows.push(rowId) }// 左上角提交按鈕的回調函數 function onSubmit() {// Step 1: Clear selected data's price:selectedRows.forEach(rowId => data[rowId].price = 0);// Step 2: Sync to local store:syncToLocalStoreAction(data);// Step 3: Sync to remote backend:syncToBackendRequest(data);// Step 4: Clear view model:selectedRows = []; }

有幾個問題我要需要考慮:

  • 如果上面的這段代碼書寫在某個 React 組件中,如果某天我們需要切換為另一個 UI 框架時,這部分代碼我們可能需要原封不動的照抄一遍,但你可以看到,上述代碼并沒有使用到 React 技術特定的接口或者語法。理論上來說時可以無縫移植的
  • 即使需求不是遷移框架,而是需要上述邏輯在同一個應用的不同組件中重用,例如上面截圖是清除水果的價格,另一個頁面需要清除 3C 產品的商品價格, 抄一遍似乎也有一些多余。這樣就可能產生“散彈式修改”的代碼壞味道
  • 最后一個問題是測試,對于相同的邏輯,我們可不希望當邏輯復用時需要編寫的測試也要加倍。

再次提醒以上考慮的出發點是我們在第一章討論的非功能需求,即可維護性和測試。如果你不在乎非功能需求,那么接下來的內容對你的意義并不大。

服務層

從上面的三點敘述中,我們不難得出我們需要進一步解決的問題:

  • 即表現層邏輯、業務邏輯、與視圖三者其實并非強相關的,尤其是表現層邏輯可以與視圖使用的具體技術棧無關。
  • 表現層邏輯需要和視圖進行分離,以便于復用。
  • 同時注意上面 onSubmit 回調函數中的內容,它其實描述的是一些列流程,在這個提交操作中,我首先需要做什么、其次需要做什么以及最后需要做什么。這樣的流程是用戶使用的其中一個場景,也算是其中一個用例。這些用戶用例本質上是和表現層的技術無關的,無論是使用 React 還是 Vue 都需要將它們實現。所以我們可以把它們作為獨立的模塊與視圖隔離并且封裝起來。借用后端的概念,我們可以把這類模塊、這種規則的分層稱之為Service Layer,后文中使用服務層稱呼它。

    封裝用戶用例只是服務層的實現,再往上抽象點看,它定義的其實是應用的邊界。因為無論操作指令來自于用戶界面,或者想象它終有一天被移植到命令行界面,操作指令來自于命令行輸入,所有可行的操作以及需要對這些操作做出的響應都不過封裝于該層。該層決定了應用能做什么不能做什么。直接搬用 Martin Fowler 對于服務層的完整定義如下:

    Defines an application's boundary with a layer of services that establishes a set of available operations and coordinates the application's response in each operation.

    但不難看出服務層其實也只是“中介”而已,在服務層的實現里它依然需要調用其它的模塊來實現功能,最需要互動的就是各種領域模型。這個方面的相關的問題我們待會再談

    在后端開發中,服務層不會關心視圖,視圖的操作通常以 API 的形式到達的這里,所以并不存在表現層邏輯的問題。但是涉及在界面的開發中我們必須要解決這個問題,我們都同意表現層邏輯需要和視圖的實現分離,那么分離之后放哪呢?目前看來服務層是一個不錯的選擇,因為 1) 表現層邏輯確實和用例相關;2) 服務層也確實是和視圖分離的。服務層和視圖的合作方式也非常簡單,通常是把事件委托給服務層處理而已。React 示例代碼如下:

    function TodoComponent() {const serviceLayer = new ServiceLayer();function onComplete(todo) {serviceLayer.completeTodo(todo);} function onDelete(todo) {serviceLayer.deleteTodo(todo);}return (/.../) }

    就像上面說的,服務層其實是一個舶來品。服務層在后端上下文中需要解決的問題與前端并不重疊。但主動權掌握在我們手中,我們可以豐富服務層的職責,讓它為我們提供更好的服務——比如 selector。

    selector 的作用并不是僅僅把組件所需要的屬性選擇出來而已。它是組件與領域模型之間的緩。因為組件并不知道自己會被用在何處,所以它不需要也不應該關心在它所屬的應用內 store 包含的是什么樣的業務模型。如果讓組件直接擁有關于 store 的知識反會產生耦合。這個問題可能在 React 中會有所緩解,因為有 mapStateToProps 和 reselector 作為天然的屏障。但是在 Angular 中,因為依賴注入的關系很容易產生這一的問題。比如直接和 store 打交道的例子:

    ```javascript @Component({ selector: 'List', templateUrl: './list.component.html', styleUrls: ['./list.component.scss'] }) export class ListComponent implements OnInit { constructor(private todoStore: TodoStore) { }

    ngOnInit() { this.data = this.todoStore.todos.filter(t => !!t.active); } } ```

    在上面這段代碼中,ListComponent 是通用的表現層組件,但是確直接對具體的 TodoStore 進行引用,造成了和具體業務的強耦合,降低了組件復用性。可以修改為對 ServiceLayer的引用

    @Component({selector: 'List',templateUrl: './list.component.html',styleUrls: ['./list.component.scss'] }) export class ListComponent implements OnInit {constructor(private serviceLayer: ServiceLayer) {}ngOnInit() {this.data = this.serviceLayer.getData();} }

    這樣一來ListComponet 的職責會更加明確更加通用,當開發人員需要僅僅對視覺功能進行修改時可以降低業務邏輯造成的干擾。又或者當開發人員需要修改獲取數據的邏輯時僅僅修改 serviceLayer.getData方法即可,這也呼應了我們之前所說的單一職責。

    同時補全 UI 與服務層的獲取數據的流程,我們便得到了最終上圖的結果。注意,上圖中數據流依然是單向的。也就是說上圖中的架構設計在 React 或者是 Redux 中是適用的。

    截止到現在“服務層”似乎已經有些偏離它原始的涵義,我更愿意親切的稱之為 Presenter,MVP(Model View Presenter) 中的 Presenter

    MVP

    MVP 的實現有兩類,一類稱為 Passive View,另一類稱為 Supervising Controller

    • Passive View: 顧名思義如 passive(被動)所示,在這個模式中 View 是不包含任何邏輯的,它是被動的被調用方。View 和 Model 完全被Presenter 隔開,Presenter 充當中介的角色分別與兩者溝通。Presenter 可以監聽的 Model 層上的一些事件。當數據發生修改時,事件就會被觸發,接著 Presenter 再通過 View 上暴露的方法對 View 進行數據更新。

    • Supervising Controller: Presenter 會負責響應用戶的 UI 操作,但與 Passive View 最大的不同在于 View 會直接與 Model 打交道,并且與 Model 進行數據綁定。在有的實現中 Presenter 的職責還包括就是將 Model 數據傳遞給 View

    相對于 MVC,MVP 在桌面端和 web 端的概念更統一一些。

    所以很顯然,Supervising Controller 模式與我們上面描述的服務層模式,乃至 Redux 都更加契合。總結下來,前端領域 View、Presenter、Model 的分別職責如下:

    至于如何實現,我認為目前的所有框架都支持這一套架構的實現,只不過 Redux 類型的框架可能相對 Object 類型的框架實現起來會別扭一些。

    這樣的分配會影響到我們下一個談論的話題,測試。

    測試

    不知道大家是否熟悉上圖中的測試金字塔,簡單來說我們可以根據測試所涉及的范圍來將測試類型劃分為這些等級,最底層的是粒度最小的單元測試,最頂層的端到端的應用級測試。我在Google搜索測試金字塔的時候不同圖片會有少許差異,但總的來說和我上面的描述大致相似。

    就我個人的經驗而言,在編寫測試時不可能覆蓋所有這些類型的測試,這當中有交付壓力與人力成本的考慮。

    我們再次回到最終版本的這個圖:

    在經過重新對代碼進行組織之后,現在我們需要回答這個問題,應該對哪些代碼進行測試?

    • UI:我最不建議對純 UI 代碼進行測試,這里所說的純 UI 指的是類似于 React 中的 Dump Component. 因為UI 測試的效率是非常底下的,相對于純粹代碼性的測試,不僅 UI 測試的啟動和運行都略遜一籌,編寫起來也費勁,通常你需要查找出不同的元素,然后模擬的用戶的操作,最后再對頁面元素做驗證。

    建議 UI 測試只在非用不可的情況下編寫,比如你設計了一個極其復雜的組件,例如 handsontable, 它純粹是表現層的,組件對用戶操作的反饋是其中非常重要的功能,那么此時對 UI 的測試才是有價值的。

    • Service Layer / Presenter:這里是我最推薦編寫測試的地方。首先這里的測試對象通常面向的是代碼,因為服務層通常由 store 或者是類進行封裝;其次這部分的邏輯非常重要,它包含的是所有的用戶用例,用戶用例即“用戶能干什么”的終極體現。如果這部分都沒法保證的話,那么我們的應該基本上沒有任何用處。

    在對用戶用例進行測試的同時,其實也間接的在對業務模型進行測試。因為你最終需要驗證用戶的一頓操作之后業務的數據是否如期望所示,例如是否按期望進行了刪除、是否發生了修改

    當然凡事沒有絕對,如果你的應用內有非常重要的功能,例如工具類中的一個非常重要的算法,嚴格的業務模型,那么也可以單獨對這些功能做單元測試。

    關于測試,我推薦閱讀 Kent 的關于前端測試的一系列的文章:The Testing Garden of Kent C. Dodds,我個人是是非常贊同他主張的一些列測試策略,例如:

    • Test use cases, not code.
    • Write tests. Not too many. Mostly integration.

    總結

    以上是生活随笔為你收集整理的前端切换视图_前端架构 101(五):从 Flux 进化到 Model-View-Presenter的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。