Redux源码分析之combineReducers
combineReducers:把recuder函数们,合并成一个新的reducer函数,dispatch的时候,挨个执行每个reducer
我们依旧先看一下combineReduers的使用效果
let { createStore, bindActionCreators, combineReducers } = self.Redux //默认state let todoList = [], couter = 0 // reducer let todoReducer = function (state = todoList, action) { switch (action.type) { case 'add': return [...state, action.todo] case 'delete': return state.filter(todo => todo.id !== action.id) default: return state } }, couterReducer = function (state = couter, action) { switch (action.type) { case 'add': return ++state case 'decrease': return --state default: return state } } var reducer = combineReducers({ todoReducer, couterReducer }) //创建store let store = createStore(reducer) //订阅 function subscribe1Fn() { // 输出state console.log(store.getState()) } store.subscribe(subscribe1Fn) // action creater let actionCreaters = { add: function (todo) { //添加 return { type: 'add', todo } }, delete: function (id) { return { type: 'delete', id } } } let boundActions = bindActionCreators(actionCreaters, store.dispatch) console.log('todo add') boundActions.add({ id: 12, content: '睡觉觉' }) let boundAdd = bindActionCreators(actionCreaters.add, store.dispatch) console.log('todo add') boundAdd({ id: 13, content: '陪媳妇' }) let counterActionCreater = { add: function () { return { type: 'add' } }, decrease: function () { return { type: 'decrease' } } } let boundCouterActions = bindActionCreators(counterActionCreater, store.dispatch) console.log('counter add:') boundCouterActions.add() console.log('counter decrease:') boundCouterActions.decrease()
下面是执行结果
我们一起分析一下:
- 执行todo add的时候,看到counterReducer和 todoReducer的数据都有更新,说明两个reducer都执行了。
- 执行counter add的时候,同样两个recuder都执行,但是因为没有参数,加入的是无效数据,这里就提示我们,是不是该进行一些必要的参数判断呢
- 执行counter decrease的时候,同样两个reducer都执行,但是 todoReducer没有tyepe为decrease的action处理函数,当然没有任何产出
我们再回归源码,删除一些判断的代码逻辑,简化后如下:
- 过滤一下reducer,把reducer和key都存起来
- 返回一个新的reducer函数,新的reducer函数执行的时候,便利存起来的reducer,挨个执行
export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) const finalReducers = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) return function combination(state = {}, action) { let hasChanged = false const nextState = {} for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
这里额外的分析一下,当store的recuder是复合型的时候,怎么初始化state的
createStore传入的第一个参数recuder,是调用 combineReducers 新生成的reducer(依旧是一个函数)
createStore方法返回之前,会这样一下dispatch({ type: ActionTypes.INIT }),disptach的里面我们就关心下面几句
try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false }
也就是执行一下新的reducer,我们继续切换到新的reducer代码里面,同样只关心下面的代码
return function combination(state = {}, action) { let hasChanged = false const nextState = {} for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state }
我们就看我们这个例子 combineReducers({ todoReducer, couterReducer }), 那么上面的key就会是 todoReducer, couterReducer, 那么初始化完毕的state得数据结构就是这样的
{todoReducer:....,couterReducer:......},
有人会想,初始化state,你上次不是用了两种方式,我这里只能说对不起,当你用的是复合型的reducer初始化state的时候,你用第二个参数来初始化state行不通的,
因为为了方便解析代码,上面我是省略了一部分的 ,下面再看看更完整一点的代码(我还是省略了一下)
export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) const finalReducers = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) let shapeAssertionError try { assertReducerShape(finalReducers) } catch (e) { shapeAssertionError = e } return function combination(state = {}, action) { if (shapeAssertionError) { throw shapeAssertionError } ....... }
这里如果你没通过 aessertRecucerShape检查,是没法进行下去的,我们那看看aessertRecucerShape是个啥玩意,看备注。
function assertReducerShape(reducers) { Object.keys(reducers).forEach(key => { const reducer = reducers[key] const initialState = reducer(undefined, { type: ActionTypes.INIT }) // 传入 undefined,让recuder默认值生效, if (typeof initialState === 'undefined') { // 如果没有默认值,返回的state就是undefined,然后抛出异常 throw new Error( `Reducer "${key}" returned undefined during initialization. ` + `If the state passed to the reducer is undefined, you must ` + `explicitly return the initial state. The initial state may ` + `not be undefined. If you don't want to set a value for this reducer, ` + `you can use null instead of undefined.` ) } const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.') if (typeof reducer(undefined, { type }) === 'undefined') { // 这个主要是防止在recuder你真的自己定义了对type为ActionTypes.INIT处理,创建一个随机的type,测试一下,你应该返回的是有效的state
throw new Error( `Reducer "${key}" returned undefined when probed with a random type. ` + `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` + `namespace. They are considered private. Instead, you must return the ` + `current state for any unknown actions, unless it is undefined, ` + `in which case you must return the initial state, regardless of the ` + `action type. The initial state may not be undefined, but can be null.` ) } }) }
这就说明了上述的问题,(-_-)
回顾
1. combineReducers 的参数是一个对象
2. 执行结果返回的依旧是一个reducer
3. 通过combineReducers 返回的reducer创建的store, 再派发某个action的时候,实际上每个内在的reducer都会执行
4. createStrore使用合成的reducer创建的store, 他再派发action返回的是总的大的state