Redux 中的 combineReducers
能让咱们很方便地把多个 reducers 组合起来,成为一个新的 reducer。
然而,随着咱们的应用变得愈来愈复杂,combineReducers
有可能不能知足咱们的需求。
正如 Redux 官方文档所说:html
This helper is just a convenience! You can write your own combineReducers that works differently, or even assemble the state object from the child reducers manually and write a root reducing function explicitly, like you would write any other function.react
combineReducers
只是方便咱们使用而已,咱们能够自定义一个彻底不一样的 combineReducers
来知足咱们特殊的需求。git
咱们先回忆一下 reducer 的写法:github
const reducer = (oldState, action) => newState;
reducer 是一个普通的函数,接受两个参数:oldState
和 action
,而后返回一个 newState
。
为了把多个 reducers 组合起来,咱们一般会用 Redux 自带的 combineReducers
来实现:redux
const rootReducer = combineReducers({ key1: key1Reducer, key2: key2Reducer });
先留意一下咱们传了什么给 combineReducers
:api
{ key1: function(state.key1, action) { /*...*/ }, key2: function(state.key2, action) { /*...*/ }, }
好了,让咱们先来想想,通过 combineReducers
的处理以后,咱们获得了什么呢?
不用想了,很显然咱们获得了一个新的 reducer。
那这个新的 reducer 又长什么样呢?函数
const rootReducer = (oldState, action) => newState;
你应该不会惊讶,由于全部 reducer 都长这个样子,即便它是已经被组合过的 reducer,它也是长这个样子。
如今你应该猜到 combineReducers
作了什么了吧?其实它最基本形态是这样子的:ui
function combineReducers(reducers) { return function (state, action) { /*...*/ }; }
它接受 reducers
做为参数,而后返回一个标准的 reducer 函数。spa
注意:
其实到了这一步,咱们就能够自定义combineReducers
了,咱们彻底能够写一个相似的函数,而后在里面写各类switch...case
语句来达到自定义的目的。
但我以为咱们仍是先看看 Redux 自带的combineReducers
作了什么比较好,由于咱们自定义的combineReducers
颇有可能须要原来的功能。设计
还记得我刚才叫你留意的地方吗?没错,就是下面这个:
// reducers { key1: function(state.key1, action) { /*...*/ }, key2: function(state.key2, action) { /*...*/ } }
咱们来回想一下 store.dispatch(action)
的过程:当一个 action
触发的时候,全部 reducers 都应该响应这个 action
,作出相应的改变,最后返回一个新的 store
。
对着上面这个结构,咱们其实很容易就能写出这样的效果,还能加上一些其余的处理:
function reCombineReducers(reducers) { return function (state, action) { switch (action.type) { case SP_ACTION: return Object.assign({}, state, { /* do something */ }); default: return Object.keys(reducers) .map(k => ({ [k]: reducers[k](state[k], action) })) .reduce((prev, next) => Object.assign({}, prev, next)); } } }
上面的例子模拟了原来 combineReducers
的功能,还对 SP_ACTION
进行了特殊的处理,很简单吧!
然而,上面的例子虽然模拟了 combineReducers 的功能,但失去了 combineReducers 的检查对象变化的功能,由于如今的 default block 中会返回一个全新的对象。
有没有方法能够既保留 combineReducers 的所有功能,又能扩展它呢?
其实很简单,咱们只要利用 combineReducers 返回的函数就能够了!
(感谢 liximomo 指出上面例子中的缺陷)
function reCombineReducers(reducers) { let fn = combineReducers(reducers); return function (state, action) { switch (action.type) { case SP_ACTION: return Object.assign({}, state, { /* do something */ }); default: return fn(state, action); } } }
按照 Redux 的原则,不一样的 reducer 应该相互独立的,它们之间不该该有任何依赖。
这个原则看着是很美好的,但在实际使用中仍是会有一些例外的状况。
一个很简单的例子,也是我遇到过的例子,就是实现一个简单的表格 (其实个人状况复杂的多,须要实现相似 Excel 那样的操做,同时支持其余额外的功能)。
咱们先来设计一下 state
:
// state { rows: { ... }, cells: { ... }, data: { ... } }
rows
, cells
, data
都会响应一些特定的 action
(如 CHANGE_ROW_PROPS
, CHANGE_CELL_PROPS
, CHANGE_DATA
),作出相应的改变,这些都是咱们所指望的。
然而,当出现一些特殊的 action (如 GET_TABLE_SUCCESS
,表示成功从服务端获取数据) 的时候,灾难就出现了:
全部的 reducer 都须要监听 GET_TABLE_SUCCESS
这个 action,这意味着若是咱们有 n 个 reducer 的话,咱们就须要修改 n 个文件!
当我再加上 UPDATE_TABLE_SUCCESS
,REMOVE_TABLE_SUCCESS
之类的 action
时,我要再修改 n 个文件!
这不合理啊,为何我加一个简单的功能,须要修改这么多文件,最重要的是,这些修改都是很是相似!
这时候,咱们就须要自定义 combineReducers
来解决咱们的需求拉:
function reCombineReducers(reducers) { let fn = combineReducers(reducers); return function (state, action) { switch (action.type) { case GET_TABLE_SUCCESS: case UPDATE_TABLE_SUCCESS: return Object.assign({}, state, action.payload.table); case REMOVE_TABLE_SUCCESS: return initState; default: return fn(state, action); } } } const table = reCombineReducers({ sections, suites, rows, cells, toys, data, logics })
怎么样,是否是比修改多个文件舒服不少?
(完)
http://scarletsky.github.io/2...
http://redux.js.org/docs/api/...
https://github.com/reactjs/re...