Redux 进阶:拆分 reducer
❓ state 过于庞大怎么办
因为 Redux 应用中只有一个 store,因此,当应用的交互越来越复杂时,store 必定会变成一个非常庞大的对象。这时,我们可以使用 Redux 提供的 combineReducers()
方法将多个小的 reducer 组合成一个 rootReducer,而每个小的 reducer 只关心自己负责的 action.type
,因为在整个应用中,有些 state 之间是互不关联的,因此我们可以拆分 reducer,不同的 reducer 处理不同的 action.type
。
假设现在有两个逻辑:一个计数器和一个 TODO。很明显这两个逻辑之间没有任何的联系,因此我们可以拆分成两个 reducer,然后再组合起来传给 createStore()
:
// 计数器 reducer function counter(state = 0, action) { switch(action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state -1; default: return state; } } // TODO reducer function todos(state = [], action) { switch(action.type) { case 'ADD_TODO': return state.concat([action.text]); default : return state; } } // 组合成一个 rootReducer const rootReducer = combineReducers({ counter, todos }); // 根据 rootReducer 创建 store const store = createStore(rootReducer) console.log(store.getState());
结果
可以看到,store.getState()
中的 key 变成了子 reducer 的函数名,达到了拆分之间没有联系的 state 的目的。而 key 变成了子 reducer 的函数名的原因是,在组合 reducer 时的写法:
// 组合成一个 rootReducer const rootReducer = combineReducers({ counter, todos });
这里传入给 combineReducers()
方法的是一个对象,这里使用了 ES6 的语法,即当对象的 key 和 value 同名时,可以省略 key,上面的写法相当于:
const rootReducer = combineReducers({ - counter, + counter: counter, - todos + todos: todos });
当然,key 是可以随意修改的,只是修改后,对于 state 的 key 也会有所不同罢了。需要记住的是 combineReducers()
方法的参数是一个对象,对象的 value 是需要组合的 reducer,对象的key 对应的是 state 中的 key,返回的是一个组合后的 reducer 函数,可以像普通 reducer 一样传给 createStore()
方法创建 store。
与 React 结合后结果是这样的:
在 coding 之前
构建一个 Redux 应用之前,我们应该先搭建好架构,也就是设计模式的那一套东西,在 coding 之前,不妨先停下来思考一下:
代码文件的组织结构
- 按角色组织
- 按功能组织
state 树的设计
- 一个模块控制一个 state 节点
- 避免冗余数据:做到 state 的范式化,类似 SQL 关系型数据库。为解决范式化数据的性能问题,可以使用 reselect 这样的工具提高性能。
- 树形结构扁平,避免很深的层次
- 确定模块的边界:在理想情况下,我们应该通过增加代码就能增加功能,而不是修改现有的代码。
下面是一个按功能组织的文件结构:
todoList/ action.js # 定义 action creators actionTypes.js # 定义 action 类型 index.js # 把这个功能模块的文件导入,然后统一导出 reducer.js # 定义 reducer 如何处理 action views/ # 定义所有 React 组件,包括容器组件和展示组件 component.js container.js filter/ action.js actionTypes.js index.js reducer.js views/ component.js container.js