All State In Redux
在上一篇文章【Redux的副作用處理與No-Reducer開發模式】中,我們介紹瞭如何使用Redux/Redux-Saga來進行元件的狀態共享,以及副作用處理。
在隨後的開發中,我們所有的頁面,以及業務邏輯元件都使用了這一套開發模式。舉一個例子,我們有一個App搜尋的AutoComplete元件,這個元件會做如下事情:
- 從Redux的State中讀取使用者token。
- 通過使用者的token,請求後臺服務,獲取這個使用者有許可權看到的App列表。
- 將App資訊載入元件,根據使用者輸入返回對應的搜尋項。
由於這個元件需要讀取存放在Redux State中的使用者token,並且包含非同步請求,將它的狀態放入Redux中管理,並且使用Redux-Saga處理非同步請求是非常合適的。
元件複用
但是在元件的複用性上,我們遇到一個難題,由於Redux本身並不提供模組化功能,我們想要複用使用了Redux/Redux-Saga的元件時:
- 我們需要為這個元件註冊一個全域性不衝突的reducerKey。
- 我們需要修改元件所關注的action型別,因為全域性會有多個元件例項,如果action型別重複,會引起錯誤的元件狀態改變。
- 在元件被解除安裝(Umountained)後,由於儲存在Redux中的state不會被自動銷燬,我們需要手動清理元件的app列表資訊。
- 元件被解除安裝後,reducer繼續存在,會輕微的損失一些執行效能。
針對這種情形,我們開發了Redux-Arena。Redux-Arena會將Redux/Redux-Saga的程式碼與React元件匯出成一個React高階元件以供複用:
- 在高階元件被掛載(Mount)時,會自動初始化Redux-Saga的任務,初始化元件的reducer並在Redux維護的State上註冊自己的節點。
- 在高階元件被解除安裝(Unmout)時,會自動取消Redux-Saga的任務,銷燬元件的reducer並從Redux維護的State上刪除自己的節點。
- 提供元件通道機制,元件傳送的Action預設只能被元件自己的reducer接收。也可以通過配置放棄通道,接收全域性的action。
- 提供vReducerKey機制,Redux中如果元件間想共享state資訊,需要知道知道真實的節點名稱,在可複用的Redux元件中很容易引發衝突,Redux-Arena提供vReducerKey機制保證了state節點真實名稱永遠不會衝突。vReducerKey在同名時,下層元件會覆蓋掉上層元件的vReducerKey資訊。
- 提供單向的(類似flux的 one-way data flow)元件狀態和actions的共享方案,下層元件可以通過vReducerKey獲取上層元件的state和actions。
- 與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,使用文件陸續補完中。