React狀態管理大亂鬥,橫向對比Dva,Rematch,Mirror
這年頭,Redux 狀態管理框架滿天飛,前幾天在網上閒逛偶然又發現 Rematch、Mirror、Smox、Xredux,都用了一下,發現都是套瓷娃娃,大同小異,拿幾個比較歷害的來說:
無非就是類似這樣的:
model({ state: ..., reducers: { aaa(playload) bbb(playload) }, effects: { ccc(playload) ddd(playload) } }) 複製程式碼

審美疲勞了?H 起來,給大家推薦一款小鮮肉 React-coat
:
專案地址: github.com/wooline/rea…
class ModuleHandlers extends BaseModuleHandlers { @reducer public aaa(playload): State {...} @reducer private bbb(playload): State {...} @effect("ajaxLoading") public async ccc(playload) {...} @effect("loginLoading") private async ddd(playload) {...} } 複製程式碼
spring 風格?ng 風格?

可能你會說,用 Class 呀,不喜歡,我喜歡 FP 風格。我想說,這是狀態管理框架非 React UI 框架,不要為了流行 FP 就皆 FP,就象當年 JS 流行面向物件程式設計,把面向過程說成洪水猛獸。
武裝到牙齒的 TS 型別反射
React-coat 全面擁抱 Typescript,直接上圖:
action 呼叫時的型別反射:

動態載入模組時的型別反射:

Store State 結構的型別反射:

連路由引數也有型別反射:


支援單頁 SPA 和伺服器渲染 SSR 同構
- 而且 SSR 在開發時也可以享受:“熱更新”
- 還支援 SPA(單頁) + SSR(伺服器渲染)一鍵切換。
開啟專案根下的./package.json,在"devServer"項中,將 ssr 設為 true 將啟用伺服器渲染,設為 false 僅使用瀏覽器渲染
關於伺服器渲染SSR請移步另一篇文章: juejin.im/post/5c7f6e…

強大而便捷的 Dispatch Action
對比一下各大框架 Dispatch Action 的語法:
// Dva中 yield put({type: 'moduleA/increment',payload: 2}); // Rematch中 dispatch.moduleA.increment(2); // Mirror中 actions.moduleA.increment(2); // React-coat中 import moduleA from "modules/moduleA/facade"; ... await this.dispatch(moduleA.actions.increment(2)); 複製程式碼
-
語法簡潔性上,Dva 用的 saga 中的 yield put,還要寫 type 和 payload,最繁瑣。其它三款都直接用方法呼叫,更簡潔。
-
Rematch 和 Mirror 等於把所有 action 都放到一個全域性變數中去了,而 React-coat 去中心化 ,按需引入 moduleA,
更利於系統保持鬆散結構
。 -
從語義上來說 React-coat 依然顯示的保留 dispatch 關鍵字,
moduleA.actions.increment(2)
返回的是依然是 Action,dispatch(action)
作為 Redux 的基本理念得到完整的保持,Rematch 和 Mirror 已經變成傳統的 MVC 了。 -
從功能上,只有 Dva 和 React 支援
同步 effect
。其它兩款都不支援,或者是我沒發現?什麼是同步 effect?例如:- query 會觸發一個 effect,updateState 會觸發一個 reducer
- updateState 需要等待 query 執行完後再 dispatch
// Dva中使用 saga 的 put.resolve 來支援同步 effect yield put.resolve({type: 'query',payload:1}); yield put({type: 'updateState',payload: 2}); 複製程式碼
// React-coat 中可以直接 awiat dispatch await this.dispatch(thisModule.actions.query(1)); this.dispatch(thisModule.actions.updateState(2)); 複製程式碼
-
React-coat 的獨有的殺手鐗:action 名稱和引數的型別反射和智慧提示、public private 許可權的控制,讓你感受什麼才叫真正的封裝。試想下如果多人同時並行開發多個模組,你還需要為你的模組寫一大篇 API 說明文件麼?


徹底的模組化
既然是企業級應用,那模組化自然是少不了的,包括模組封裝、程式碼分割、按需載入。模組化的目的主要是拆分複雜系統、解耦與重用。
以上框架中,Rematch 和 Mirror 的模組化功能比較弱,且不優雅,略過。Dva 和 React-coat 都有強大的模組化功能,其中 Dva 可以搭配 UMI 來自動配置。
在 dva 中動態載入 model 和 component,要靠路由配置:
{ path: '/user', models: () => [import(/* webpackChunkName: 'userModel' */'./pages/users/model.js')], component: () => import(/* webpackChunkName: 'userPage' */'./pages/users/page.js'), } 複製程式碼
React-coat 中程式碼分割和路由分層而治:
- 程式碼分割只做程式碼分割,不參和路由的事,因為模組也不一定是非得用路由的方式來載入。
- 路由只做路由的事情,不參和程式碼分割的事,因為模組也不一定非得做程式碼分割。
- 一個 Module 整體打包成一個 bundle,包括 model 和 views,不至於太碎片。
// 定義程式碼分割 export const moduleGetter = { app: () => { return import(/* webpackChunkName: "app" */ "modules/app"); }, photos: () => { return import(/* webpackChunkName: "photos" */ "modules/photos"); }, } 複製程式碼
React-coat 中支援路由動態載入,也支援非路由動態載入
// 使用路由載入: const PhotosView = loadView(moduleGetter, ModuleNames.photos, "Main"); ... <Route exact={false} path="/photos" component={PhotosView} /> 複製程式碼
// 直接載入: const PhotosView = loadView(moduleGetter, ModuleNames.photos, "Main"); ... render() { const {showDetails} = this.props; return showDetails ? <DetailsView /> : <ListView />; } 複製程式碼
- Dva 以 Page UI 主線來劃分模組;React-coat 以業務功能 高內聚、低偶合 來劃分模組。後者更適合解耦與重用。
- Dva 使用集中配置、將 Page、路由、model、程式碼分割全部都集中寫在一箇中心檔案中; React-coat 去中心化 ,將各自的邏輯封裝在各自模組中,並且 model、程式碼分割、路由分層而治,互不干涉。後者更乾淨整潔。
- Dva 將每個 model 和 component 都做成一個程式碼分割包;React-coat 將一個 Module 整體做成一個程式碼分割包,前者太碎,後者更符合 bundle 概念。
- React-coat 支援路由動態載入 View,也支援非路由動態載入 View,二條腿走路步子邁得更大。
- React-coat 動態載入 View 時會自動匯入 Model,無需手工配置載入 Model,是真正的路由元件化。
更多差異還是請看: 與 DvaJS 風雲對話,是 DvaJS 挑戰者?還是又一輪子?

跨模組的呼叫與協作
在複雜的長業務流程中,跨模組呼叫與協作是少不了的,Dva、Rematch、Mirror、React-coat 都支援跨模組派發 action,跨模組讀取 State。比如:
// Mirror中 if(resphonse.success){ actions.moduleA.doSomeEffect(); actions.moduleB.doSomeEffect(); } 複製程式碼
這是一種串聯呼叫的模式,適應於一些耦合緊密的業務流。 但對於一些鬆散耦合的業務流程,最佳的方式應當是觀察者模式,或叫事件廣播模式。
場景:當 moduleA 執行了一個 action,moduleB、moduleC、moduleD...都需要執行一些各自的動作
這就是 React-coat 獨有的殺手鐗:ActionHandler 概念。
class ModuleB { //在ModuleB中監聽"ModuleA/update" action async ["ModuleA/update"] (){ await this.dispatch(this.action.featchData()) } } class ModuleC { //在ModuleC中監聽"ModuleA/update" action async ["ModuleA/update"] (){ await this.dispatch(this.action.featchData()) } } 複製程式碼

