随着应用越来越复杂,我们会将多个reducer拆分成多个独立的函数,拆分后每个函数负责独立管理state的一部分。
combineReducers辅助函数的作用,就是把多个不同的reducer函数作用 value 的 object,合并成一个最终的reducer函数,然后对这个reducer调用 createStore方法。
合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。
示例:
// 创建我们的 reducers。const _reducers = {items(items = [], { type, payload }) {if (type === 'ADD_ITEMS') items.push(payload);return items;},isLoading(isLoading = false, { type, payload }) {if (type === 'IS_LOADING') return true;return false;}};// 创建根 rootReducerconst rootReducer = combineReducers(_reducers)// createStore 接受 reducers,创建我们需要的 store。const store = createStore(rootReducer);// 拿到组合后的stateconst state = store.getState();
rootReducer 返回一个函数 combination(state = {}, action),再交给 createStore来创建我们需要的 store。
带着示例我们来看源码:
源码
先看辅助函数:
getUndefinedStateErrorMessage,如果一个reducer返回的是一个
undefined的state,就会调用function getUndefinedStateErrorMessage(key, action) {const actionType = action && action.typeconst actionDescription =(actionType && `action "${String(actionType)}"`) || 'an action'return (`Given ${actionDescription}, reducer "${key}" returned undefined. ` +`To ignore an action, you must explicitly return the previous state. ` +`If you want this reducer to hold no value, you can return null instead of undefined.`)}
getUnexpectedStateShapeWarningMessage,用于校验未知键、不是对象的
state,对于REPLACE类型,不校验。function getUnexpectedStateShapeWarningMessage(inputState,reducers,action,unexpectedKeyCache) {const reducerKeys = Object.keys(reducers)const argumentName =action && action.type === ActionTypes.INIT? 'preloadedState argument passed to createStore': 'previous state received by the reducer'if (reducerKeys.length === 0) {return ('Store does not have a valid reducer. Make sure the argument passed ' +'to combineReducers is an object whose values are reducers.')}if (!isPlainObject(inputState)) {return (`The ${argumentName} has unexpected type of "` +{}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +`". Expected argument to be an object with the following ` +`keys: "${reducerKeys.join('", "')}"`)}const unexpectedKeys = Object.keys(inputState).filter(key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key])unexpectedKeys.forEach(key => {unexpectedKeyCache[key] = true})if (action && action.type === ActionTypes.REPLACE) returnif (unexpectedKeys.length > 0) {return (`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +`Expected to find one of the known reducer keys instead: ` +`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`)}}
assertReducerShape,校验
reducer的合理性,初始化和返回值不能是undefined。function assertReducerShape(reducers) {Object.keys(reducers).forEach(key => {const reducer = reducers[key]const initialState = reducer(undefined, { type: ActionTypes.INIT })if (typeof initialState === '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.`)}if (typeof reducer(undefined, {type: ActionTypes.PROBE_UNKNOWN_ACTION()}) === 'undefined') {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.`)}})}
主入口
combineReducers 函数的主入口:
接收参数 reducers,是一个对象。
function combineReducers(reducers) {const reducerKeys = Object.keys(reducers)const finalReducers = {}for (let i = 0; i < reducerKeys.length; i++) {const key = reducerKeys[i]if (process.env.NODE_ENV !== 'production') {if (typeof reducers[key] === 'undefined') {warning(`No reducer provided for key "${key}"`)}}// reducers只能是函数, 可以排除掉不正确的if (typeof reducers[key] === 'function') {finalReducers[key] = reducers[key]}}// 最终的 Reducersconst finalReducerKeys = Object.keys(finalReducers)// This is used to make sure we don't warn about the same// keys multiple times.let unexpectedKeyCacheif (process.env.NODE_ENV !== 'production') {unexpectedKeyCache = {}}let shapeAssertionErrortry {// 校验所有 reducers 的合理性assertReducerShape(finalReducers)} catch (e) {shapeAssertionError = e}// 返回一个新的 reducer,每次dispatch一个action,都会执行一遍这里。return function combination(state = {}, action) {if (shapeAssertionError) {throw shapeAssertionError}if (process.env.NODE_ENV !== 'production') {// 非生产环境,校验stateconst warningMessage = getUnexpectedStateShapeWarningMessage(state,finalReducers,action,unexpectedKeyCache)if (warningMessage) {warning(warningMessage)}}// 是否已改变let hasChanged = false// state 是否改变的标志位。const nextState = {}// 遍历所有reducersfor (let i = 0; i < finalReducerKeys.length; i++) {const key = finalReducerKeys[i]const reducer = finalReducers[key]const previousStateForKey = state[key] // 上一个state,也就是旧的stateconst nextStateForKey = reducer(previousStateForKey, action) // 执行reducer,拿到新的state值if (typeof nextStateForKey === 'undefined') {const errorMessage = getUndefinedStateErrorMessage(key, action)throw new Error(errorMessage)}nextState[key] = nextStateForKey // 返回值赋值给nextStatehasChanged = hasChanged || nextStateForKey !== previousStateForKey // 判断state是否发生了变化}hasChanged =hasChanged || finalReducerKeys.length !== Object.keys(state).lengthreturn hasChanged ? nextState : state // state有变化就返回新的state}}
总结
combineReducers函数的作用就是将多个reducer整合起来,交给createStore创建一个新的reducers。
可以这样理解
combineReducers就是一个高阶的reducer方法,因为其返回函数combination方法接收的参数是 state和action,其返回值也是一个 state。
在每次 dispatch 时都会执行 combination 函数遍历所有reducers,再调用各自的 reducer。
