Redux 状态管理进阶使用
- 实现 state(Reducer) 的分层
- Reducer 动态插入
- 多个 store 实例实现及调试
1. 实现 state(Reducer) 的分层
前提:redux 中的 Reducer 定义的是一个纯函数,他接收 state | action 两个参数,返回一个新的 state;
combineReducers 是 redux 提供的方法,他可以用来将多个 上述格式 的 Reducer 方法合并成一个函数
1. 一般创建 store 时会通过 combineReducers 将多个 Reducer 合并,生成的 state 结构如下
// 创建 store 时
createStore(combineReducers({
home: homeReducer,
admin: adminReducer,
about: aboutReducer,
test testReducer,
}));
// 生成的 state 结构
{
home: {/*...homeState*/},
admin: {/*...adminState*/}
about: {/*...aboutState*/}
test: {/*...testState*/}
}
2. 多层的 Reducer 实现
实际上 combineReducers 参数中的每一项也可以是通过 combineReducers 生成的方法,如下
// 创建 store 时
createStore(combineReducers({
home: homeReducer,
test combineReducers({
testFirst: testFirstReducer,
testSecond: testSecondReducer
}),
}));
// 生成的 state 结构
{
home: {/*...homeState*/},
test: {
testFirst: {/*...testFirstState*/},
testSecond: {/*...testSecondState*/}
}
}
2. Reducer 动态插入
场景:某个页面的显示是动态,会根据用户权限显示不同的模块,这个时候可以给不同的模块定义不同的 Reducer, 哪个模块显示就将哪个模块的 Reducer 插入到当前运行的 store 中
redux 中的 store 实例具有一个
store.replaceReducer(reducers)
方法,这个方法可以更新当前 store 使用的 reducers, 可以利用这个方法在每次需要插入 reducer 时更新 store,
// @flow
/*
store的创建函数
*/
import {compose, createStore, applyMiddleware, combineReducers} from 'redux';
import store from '../ReduxStore';
const middleware = [];
// 辅助使用chrome浏览器进行redux调试
const shouldCompose =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
// 创建一个 store 创建函数,这个函数会链接 redux-devtool、绑定中间件、根据 reducer 创建 store 实例
const configureStore = (
reducer: Function,
defaultState: ImmutableMap<string, any>,
newMiddleware?: Array<Function> = [],
reduxDevToolConfig?: Object,
) => {
const composeEnhancers = shouldCompose
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__(reduxDevToolConfig || {})
: compose;
return createStore(
reducer,
defaultState,
composeEnhancers(applyMiddleware(...middleware, ...newMiddleware)),
);
}
// 功能 将树状结构的 reducer 通过 combineReducer 转化为 redux 可用的结构
function toCombineReducers(reducers: Object) {
const tempReducers = Object.keys(reducers).reduce((prev, current) => {
const currentReducer = reducers[current];
prev[current] = _.isFunction(currentReducer)
? currentReducer
: toCombineReducers(currentReducer);
return prev;
}, {});
return combineReducers(tempReducers);
}
/*
* 给 store 新增 注册|删除|重置 Reducer 的功能
* */
type ReducerType = {
[string]: Function | ReducerType
}
const generateStore = (
reducer: ReducerType,
middleware?: Array<Function>,
reduxDevToolConfig?: Object,
) => {
// 创建一个 store 实例
const store = configureStore(
toCombineReducers(reducer),
{},
middleware,
reduxDevToolConfig,
);
/*
* 给 store 实例挂载两个属性,分别存储创建时的 reducer 和 创建后动态添加的 reducer
* */
store.asyncReducers = {};
store.staticReducers = reducer;
/*
* 给 store 实例挂载增加 reducer 的方法
* */
store.injectReducer = (
reducerName: string,
newReducer: ReducerType,
parentReducerName?: string, // 这里的 parentReducerName 支持一个字符串:'parent'; 也支持由'.'链接的字符串: 'parent.subParent'
) => {
if (!parentReducerName) {
const isRepeated =
_.has(store.staticReducers, reducerName) || _.has(store.asyncReducers, reducerName);
// reducerName 不可重复
if (isRepeated) {
throw new Error(`${reducerName} already exists in store, Please rewrite it`);
}
// 更新 reducer
store.asyncReducers[reducerName] = newReducer;
// store 更新
return store.replaceReducer(
toCombineReducers({
...store.staticReducers,
...store.asyncReducers,
}),
);
}
// 由于 asyncReducers 与 staticReducers 不会重复所以以下判断 一正一负或者都是 false
const isStatic = _.has(store.staticReducers, parentReducerName);
const isAsync = _.has(store.asyncReducers, parentReducerName);
if (isStatic) {
const oldReducer = _.get(store.staticReducers, parentReducerName);
// 如果是函数则表示没有可扩展设定,需要抛出错误
if (_.isFunction(oldReducer)) {
throw new Error(
`${parentReducerName} is is not extensible, please check ${parentReducerName} module and return a "Object" instead of "Instance" `,
);
}
_.set(store.staticReducers, parentReducerName, {
...oldReducer,
[reducerName]: newReducer,
});
} else if (isAsync) {
const oldReducer = _.get(store.asyncReducers, parentReducerName);
// 如果是函数则表示没有可扩展设定,需要抛出错误
if (_.isFunction(oldReducer)) {
throw new Error(
`${parentReducerName} is is not extensible, please check ${parentReducerName} module and return a "Object" instead of "Instance" `,
);
}
_.set(store.asyncReducers, parentReducerName, {
...oldReducer,
[reducerName]: newReducer,
});
} else {
// 都为 false 则重新创建一个
_.set(store.asyncReducers, parentReducerName, {[reducerName]: newReducer});
}
return store.replaceReducer(
toCombineReducers({
...store.staticReducers,
...store.asyncReducers,
}),
);
};
// 增加 移除方法
store.removeReducer = (reducerName: string) => {
if (_.has(store.asyncReducers, reducerName)) {
store.asyncReducers = _.omit(store.asyncReducers, [reducerName]);
store.replaceReducer(
toCombineReducers({
...store.staticReducers,
...store.asyncReducers,
}),
);
}
};
// 增加重置方法
store.resetReducer = (excludeAsyncReducer: boolean) => {
store.replaceReducer(
toCombineReducers({
...store.staticReducers,
...(excludeAsyncReducer ? {} : store.asyncReducers),
}),
);
};
return store;
};
export default generateStore;
// 动态插入 Reducer
store.injectReducer('storeName', storeNameReducer, 'parent.subParent');
// 最后数据结构
/*
{
parent: {
subParent: {
storeName: {...storeNameState}
}
}
}
*/
多个 store 实例实现及调试
react-redux 提供的 Provider组件可以用于将 store 与其包裹的子组件进行连接,子组件通过 connect 高阶函数来拿到 store 中的 state; 针对于 多个 store 的情况,connect 会连接到距离最近的父级 Provider
结合上一个例子 ‘动态插入 Reducer ’ 只要使用
generateStore
函数重新创建一个 store 实例即可,使用时需要将新创建的 store 通过<Provider>
组件绑定到需要的模块上 具体代码参考 - 源码 中的/src/modules/HomeTwo/
模块
这里要说明的时 我们在创建 store 时在开发环境会链接到 redux-devtool 在这个链接过程中可以传递一个参数给 当前 store 实例命名,如下
const composeEnhancers = shouldCompose
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({name: 'store 实例名称'})
: compose;
const store = createStore(
reducer,
defaultState,
composeEnhancers(applyMiddleware(...middleware, ...newMiddleware)),
);
在开发工具中按如下方法切换实例
参考
- 源码 在当前文档的基础上做了深度的封装(flow 做类型检查,请忽略)