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,使用文档陆续补完中。