原文連接, 若是感興趣能夠加QQ羣: 157937068, 一塊兒交流。前端
本次分享是帶你們瞭解什麼是 mvvm,mvvm 的原理,以及近幾年產生了哪些演變。react
同時借 mvvm 這個話題拓展到對各種前端數據流方案的思考,造成對前端數據流總體認知,幫助你們在團隊中更好的作技術選型。編程
Mvvm 是指雙向數據流,即 View-Model 之間的雙向通訊,由 ViewModel 做橋接。以下圖所示:redux
而單向數據流則去除了 View -> Model 這一步,須要由用戶手動綁定。swift
許多前端框架都內置了 Mvvm 功能,好比 Knockout、Angular、Ember、Avalon、Vue、San 等等。前端框架
而就像 Redux 同樣,Mvvm 框架中也出現了許多與框架解耦的庫,好比 Mobx、Immer、Dob 等,這些庫須要一箇中間層與框架銜接,好比 mobx-react、redux-box、dob-react。解耦讓框架更專一 View 層,實現了庫與框架靈活搭配的能力。數據結構
解耦的數據流框架也詮釋了更高抽象級別的 Mvvm 架構,即:View - 前端框架,Model - (mobx, dob),ViewModel - (mobx-react, dob-react)。架構
同時也實現了數據與框架分離,便於測試與維護。好比下面的例子,左邊是框架無關的純數據/數據操做定義,右邊是 View + ViewModel:app
Angluar 早期的髒檢測機制雖然開創了 mvvm 先河,但監聽效率比較低,須要 N + 1 次確認數據是否有聯動變化,就像下圖所示:框架
如今幾乎全部框架都改成 getter/setter 劫持實現監聽,任何數據的變化均可以在一個事件循環週期內完成:
早期一些 Mvvm 框架須要手動觸發視圖刷新,如今這種作法幾乎都被原生賦值語句取代。
下圖的代碼語法雖爲 mutable,但產生的結果多是 mutable,也多是 immutable,取決於 mvvm 框架內置實現機制:
因爲 mvvm 支持了 mutable 與 immutable 兩種寫法,因此對於 mutable 的底層,咱們使用左圖的 connect 語法,對於 immutable 的底層,須要使用右圖的 conenct 語法:
[圖片上傳失敗...(image-b7408b-1522595335875)]
對左圖而言,因爲 mutable 驅動,全部數據改動會自動調用視圖刷新,所以不但更新能夠一步到位,並且能夠數據全量注入,由於沒用到的變量不會致使額外渲染。
對右圖,因爲 immutable 驅動,自己並無主動驅動視圖刷新能力,因此當右下角節點變動時,會在整條鏈路產生新的對象,經過 view 更新機制一層層傳導到要更新的視圖。
講到 mvvm 的原理,先從 TFRP 提及,詳細能夠參考 dob-框架實現 這裏以 dob 框架爲例子,一步步介紹瞭如何實現 mvvm。本文簡單作個介紹。
autorun 是 TFRP 的函數效果,即集成了依賴收集與監聽,autorun 背後由 reaction 實現。
以下圖所示,autorun 是 subscription 套上 track 的 reaction,而且初始化時主動 dispatch,從入口(subscription)處激活循環,完成 subscription -> track -> 監聽修改 -> subscription 完成閉環。
每一個 track 在其執行期間會監聽 callback 的 getter 事件,並將 target 與 properityKey 存儲在二維 Map 中,當任何 getter 觸發後,從這個二維表中查詢依賴關係,便可找到對應的 callback 並執行。
因爲 autorun 與 view 的 render 函數很像,咱們在 render 函數初始化執行時,使其包裹在 autorun 環境中,第 2 次 render 開始遍剝離外層的 autorun,保證只綁定一遍數據。
這樣 view 層在本來 props 更新機制的基礎上,增長了 autorun 的功能,實現修改任何數據自動更新對應 view 的效果。
Mvvm 全部已知缺點幾乎都有了解決方案。
用過 Mobx 的同窗都知道,給 store 添加一個不存在的屬性,須要使用 extendObservable
這個方法。這個問題在 Dob 與 Mobx4.0 中都獲得瞭解決,解決方法就是使用 proxy
替代 Object.defineProperty
:
因爲 getter/setter 沒法得到當前執行函數,只能經過全局變量方式解決,所以 autorun 的 callback 函數不支持異步:
因爲 reaction 特性,只支持同步 callback 函數,所以 autorun 發生嵌套時,極可能會打亂依賴綁定的順序。解決方案是將嵌套的 autorun 放到執行隊列尾部,以下圖所示:
mutable 最被人詬病的一點就是沒法作數據快照,不能像 redux 同樣作時間回溯。有問題天然有人會解決,Mobx 做者的 Immer 庫完美的解決了問題。
原理是經過 proxy 返回代理對象,在內部經過淺拷貝替代對對象的 mutable 更改。具體原理能夠參考個人這篇文章:精讀 Immer.js 源碼
mvvm 函數的 Action 因爲支持異步,許多人會在 Action 中發請求,同時修改 store,這樣就沒法將請求反作用隔離到 store 以外。同時對 store 的 mutable 修改,自己也是一種反作用。
雖然能夠將請求函數拆分到另外一個 Action 中,但人爲因素沒法徹底避免。
自從有了 Immer.js 以後,至少從支持元編程的角度來看,mutable 並不必定會產生反作用,它能夠是零反作用的:
function inc(obj) { return produce(obj => obj.count++) }
上面這種看似 mutable 的寫法實際上是零反作用的純函數,和下面寫法等價:
function inc(obj) { return { count: obj.count + 1, ...obj } }
而對反作用的隔離,也能夠作出相似 dva 的封裝:
Mvvm 在項目中 stores 代碼結構也變幻無窮,這裏列出 4 種常見形式。
mobx 開創了最基本的 mvvm store 組織形式,基本也是各內置 mvvm 框架的 store 組織形式。
dob 在 store 組織形式下了很多功夫,經過依賴注入加強了 store 之間的關聯,實現 stores -> action 多對一的網狀結構。
mobx-state-tree 是典型結構化 store 組織的表明,這種組織形式適合一體化 app 開發,好比不少頁面之間細粒度數據須要聯動。
類 dva 是一種集成模式,是針對 redux 複雜的樣板代碼,思考造成的簡化方案,天然集成與約定是簡化的方向。
另外這種方案更像一層數據 dsl,得益於此,同一套代碼能夠擁有不一樣的底層實現。
Mvvm 與 Reactive programming 都擁有 observable 特性,經過下面兩張圖能夠輕鬆區分:
上面紅線是 mvvm 的 observable 部分,這裏指的是數據變化的 autorun 動做。
上面紅線是 Reactive programming 的 observable 部分,指的是數據源派發流的過程。
既然 redux 能夠與 rxjs 結合(redux-observable),那麼 mvvm 應該也能夠如此。
下面是這種方案的構想:
rxjs 僅用來隔離反作用與數據處理,mvvm 擁有修改 store 的能力,而且精準更新使用的 View。
根據業務場景指定數據流方案,數據流方案沒有銀彈,只有貼着場景走,才能找到最合適的方案。
瞭解到 mvvm 的發展與演進,讓不一樣數據流方案組合,你會發現,數據流方案還有不少。