React、Redux與複雜業務元件的複用

微匯金融大前端發表於2017-09-10

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";
import * as actions from "./actions";
import state from "./actions";
import reducer from "./reducer";
import ComponentA from "./ComponentA";

export default bundleToComponent({
  Component: ComponentA,
  actions,
  state,
  reducer
});
複製程式碼

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

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

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createArenaStore } from "redux-arena";
import ComponentA from "./ComponentA";//上面匯出的高階元件

const store = createArenaStore();

const app = document.getElementById("app");
ReactDOM.render(
  <Provider store={store}>
    <ComponentA />
  </Provider>,
  app
);
複製程式碼

非常的簡單。

通訊隔離

每個Scene預設只會接受自己所傳送的action,其他Scene或者原生Redux繫結的action,將會被忽略。

舉個例子:

import { bundleToComponent } from "redux-arena/helper";
import * as actions from "./actions";
import reducer from "./reducer";
import ComponentA from "./ComponentA";

export const ComponentA1 = bundleToComponent({
  Component: ComponentA,
  actions,
  reducer
});

export const ComponentA2 = bundleToComponent({
  Component: ComponentA,
  actions,
  reducer
});
複製程式碼

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";
import * as actions from "./actions";
import reducer from "./reducer";
import ComponentA from "./ComponentA";

export default bundleToComponent({
  Component: ComponentA,
  actions,
  reducer,
  options: {
    vReducerKey: "a1"
  }
});
複製程式碼

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

import { bundleToComponent } from "redux-arena/helper";
import state from "./state";
import actions from "./actions";
import ComponentAChild from "./ComponentAChild";

export default bundleToComponent({
  Component: ComponentA,
  state,
  actions,
  propsPicker:(state,actions,allState,{ a1 })=>({
    name: state.name,  //Scene的state
    actions,  //Scene的actions
    a1Name: allState[a1.reducerKey].name,  //ComponentA的state
    a1Actions: a1.actions   //ComponentA的actions
  })
});
複製程式碼

這樣,我們就實現了元件間的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";

function * doSomthing({ payload }){
  yield* setSceneState({ payload })
}

export function* saga (){
  yield takeLatestSceneAction("DO_SOMETHING", doSomthing)
}
複製程式碼


Redux-Arena的Github地址:https://github.com/hapood/redux-arena/blob/master/README.zh-CN.MD,具體使用可以參考專案目錄下的example,使用文件陸續補完中。


相關文章