標籤:

React、Redux與複雜業務組件的復用

All State In Redux

在上一篇文章【Redux的副作用處理與No-Reducer開發模式】中,我們介紹了如何使用Redux/Redux-Saga來進行組件的狀態共享,以及副作用處理。

在隨後的開發中,我們所有的頁面,以及業務邏輯組件都使用了這一套開發模式。舉一個例子,我們有一個App搜索的AutoComplete組件,這個組件會做如下事情:

  1. 從Redux的State中讀取用戶token。
  2. 通過用戶的token,請求後台服務,獲取這個用戶有許可權看到的App列表。
  3. 將App信息載入組件,根據用戶輸入返回對應的搜索項。

由於這個組件需要讀取存放在Redux State中的用戶token,並且包含非同步請求,將它的狀態放入Redux中管理,並且使用Redux-Saga處理非同步請求是非常合適的。

組件復用

但是在組件的復用性上,我們遇到一個難題,由於Redux本身並不提供模塊化功能,我們想要復用使用了Redux/Redux-Saga的組件時:

  1. 我們需要為這個組件註冊一個全局不衝突的reducerKey。
  2. 我們需要修改組件所關注的action類型,因為全局會有多個組件實例,如果action類型重複,會引起錯誤的組件狀態改變。
  3. 在組件被卸載(Umountained)後,由於保存在Redux中的state不會被自動銷毀,我們需要手動清理組件的app列表信息。
  4. 組件被卸載後,reducer繼續存在,會輕微的損失一些執行性能。

針對這種情形,我們開發了Redux-ArenaRedux-Arena會將Redux/Redux-Saga的代碼與React組件導出成一個React高階組件以供復用:

  1. 在高階組件被掛載(Mount)時,會自動初始化Redux-Saga的任務,初始化組件的reducer並在Redux維護的State上註冊自己的節點。
  2. 在高階組件被卸載(Unmout)時,會自動取消Redux-Saga的任務,銷毀組件的reducer並從Redux維護的State上刪除自己的節點。
  3. 提供組件信道機制,組件發送的Action默認只能被組件自己的reducer接收。也可以通過配置放棄信道,接收全局的action。
  4. 提供vReducerKey機制,Redux中如果組件間想共享state信息,需要知道知道真實的節點名稱,在可復用的Redux組件中很容易引發衝突,Redux-Arena提供vReducerKey機制保證了state節點真實名稱永遠不會衝突。vReducerKey在同名時,下層組件會覆蓋掉上層組件的vReducerKey信息。
  5. 提供單向的(類似flux的 one-way data flow)組件狀態和actions的共享方案,下層組件可以通過vReducerKey獲取上層組件的state和actions。
  6. 與Redux-Saga深度整合,在Redux-Saga中也可以選擇只發送和接收組件自己的action。

構造可復用的高階組件(Scene)

我們將每一個導出的Redux/React綁定的高階組件稱為Scene。首先我們要為Scene構造actions和reducer,然後使用bundleToComponent,導出高階組件。

import { bundleToComponent } from "redux-arena/helper";nimport * as actions from "./actions";nimport state from "./actions";nimport reducer from "./reducer";nimport ComponentA from "./ComponentA";nnexport default bundleToComponent({n Component: ComponentA,n actions,n state,n reducern});n

現在我們導出的這個組件,就可以和普通的組件一樣直接在React中使用了。

需要注意的是,Redux-Arena增強了Redux的Store,需要使用createArenaStore創建Store:

import React from "react";nimport ReactDOM from "react-dom";nimport { Provider } from "react-redux";nimport { createArenaStore } from "redux-arena";nimport ComponentA from "./ComponentA";//上面導出的高階組件nnconst store = createArenaStore();nnconst app = document.getElementById("app");nReactDOM.render(n <Provider store={store}>n <ComponentA />n </Provider>,n appn);n

非常的簡單。

通訊隔離

每個Scene默認只會接受自己所發送的action,其他Scene或者原生Redux綁定的action,將會被忽略。

舉個例子:

import { bundleToComponent } from "redux-arena/helper";nimport * as actions from "./actions";nimport reducer from "./reducer";nimport ComponentA from "./ComponentA";nnexport const ComponentA1 = bundleToComponent({n Component: ComponentA,n actions,n reducern});nnexport const ComponentA2 = bundleToComponent({n Component: ComponentA,n actions,n reducern});n

ComponentA1和ComponentA2的代碼來源都是相同的,但是他們的reducer只會接收自己的Scene內部發送的Action,其他組件發送的Action即使type相同,也會被忽略。

State與Actions共享

原生的Redux中,如果要實現state的共享,需要為組件註冊一個全局唯一的reducerKey,然後使用mapStateToProps方法,將對應state傳入props。

Redux-Arena使用了Vitural ReducerKey(vReducerKey),vReducerKey不要求全局唯一,當子組件的vReducerKey與父組件的vReducerKey相同時,子組件的vReducerKey會覆蓋掉父組件的vReducerKey的信息。

為Scene指定vReducerKey:

import { bundleToComponent } from "redux-arena/helper";nimport * as actions from "./actions";nimport reducer from "./reducer";nimport ComponentA from "./ComponentA";nnexport default bundleToComponent({n Component: ComponentA,n actions,n reducern options: {n vReducerKey: "a1"n }n});n

和mapStateToProps相似,子組件使用propsPicker取出所需要的state和actions:

import { bundleToComponent } from "redux-arena/helper";nimport state from "./state";nimport actions from "./actions";nimport ComponentAChild from "./ComponentAChild";nnexport default bundleToComponent({n Component: ComponentA,n state,n actions,n propsPicker:(state,actions,allState,{ a1 })=>({n name: state.name, //Scene的staten actions, //Scene的actionsn a1Name: allState[a1.reducerKey].name, //ComponentA的staten a1Actions: a1.actions //ComponentA的actionsn })n});n

這樣,我們就實現了組件間的state與actions的共享。

需要注意的是,這種共享模式類似Flux的one-way data flow,傳遞方向是單向的,反向的信息傳遞不能使用state,只能使用action。

如果是兄弟組件間的state共享,需要在這些兄弟組件間的某一個父組件上增加一個數據層,使用這個統一的父組件數據層共享狀態。

Redux-Saga的整合

Redux-Arena提供了一系列的Redux-Saga方法實現通訊的隔離,使在Redux-Saga中可以只接收當前Scene所派發的action。

使用setSceneState,可以方便的設置當前Scene的State。

import { setSceneState, takeLatestSceneAction } from "redux-arena/sagaOps";nnfunction * doSomthing({ payload }){n yield* setSceneState({ payload })n}nnexport function* saga (){n yield takeLatestSceneAction("DO_SOMETHING", doSomthing)n}n

Redux-Arena的Github地址:github.com/hapood/redux,具體使用可以參考項目目錄下的example,使用文檔陸續補完中。


推薦閱讀:

TAG:React | Redux |