Redux源码浅析系列(二):`combineReducer`
本站寻求有缘人接手,详细了解请联系站长QQ1493399855
上一章,我们讲解了createStore
。下面,我们来看一下combineReducer
。 在redux
中,我们禁止在应用中创建多个store
(我们这里默认讨论的都是客户端应用,同构应用不适用这条规则)。
然而,随着应用变得越来越复杂,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分。
combineReducer
就是将一个由多个reducer
函数作为value
的object
,合并成一个rootReducer
。然后就可以对这个reducer
调用createStore
。
下面,我们来具体看一下源码:
import { ActionTypes } from './createStore'
import isPlainObject from 'lodash/isPlainObject'
import warning from './utils/warning'function getUndefinedStateErrorMessage(key, action) {const actionType = action && action.typeconst actionName = (actionType && `"${actionType.toString()}"`) || 'an action'return (`Given action ${actionName}, 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.`)
}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 (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.`)}
}
/*** 遍历reducers中的reducer,检查reducer是否是符合redux规范的reducer。* 使用ActionTypes.INIT和随机type生成的action作为第二个参数,分别进行检验* 如果返回的state为undefined,则不符合redux规范。* @param {Object} reducers */
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.`)}const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')if (typeof reducer(undefined, { type }) === '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.`)}})
}export default function combineReducers(reducers) {//第一次筛选,将reducers中不是function 的键值对给筛选掉。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}"`)}}if (typeof reducers[key] === 'function') {finalReducers[key] = reducers[key]}}//第二次筛选,检测`finalReducers`中是否有不符合`redux`规范的`reducer`const finalReducerKeys = Object.keys(finalReducers)let unexpectedKeyCacheif (process.env.NODE_ENV !== 'production') {unexpectedKeyCache = {}}let shapeAssertionErrortry {assertReducerShape(finalReducers)} catch (e) {shapeAssertionError = e}return function combination(state = {}, action) {//如果刚才检测 finalReducers 发现了错误,则抛出错误。if (shapeAssertionError) {throw shapeAssertionError}//如果不是production环境则抛出warningif (process.env.NODE_ENV !== 'production') {const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)if (warningMessage) {warning(warningMessage)}}let hasChanged = falseconst nextState = {}//遍历所有的reducer,分别执行,将其计算出的state组合起来生成一个大的state.// 所以,任何action,redux都会遍历所有的reducer.for (let i = 0; i < finalReducerKeys.length; i++) {const key = finalReducerKeys[i]const reducer = finalReducers[key]//为每一个reducer计算一个state.const previousStateForKey = state[key]const nextStateForKey = reducer(previousStateForKey, action)//如果计算出来的state有undefined,抛出错误.if (typeof nextStateForKey === 'undefined') {const errorMessage = getUndefinedStateErrorMessage(key, action)throw new Error(errorMessage)}//将每一个reducer计算出来的state合并成一个大的state.nextState[key] = nextStateForKey//只要有一个reducer计算出来的state和之前的不一样,就表明状态树改变了。hasChanged = hasChanged || nextStateForKey !== previousStateForKey}return hasChanged ? nextState : state}
}
复制代码
combineReducer
的代码其实还是很简单。首先它会经过两次筛选,第一次筛选将reducers
中value
值不是function
的键值对都剔除掉,第二次筛选将reducers
中不符合redux
规范的reducer
给筛选掉。
那么,什么是符合redux
规范的reducer
呢? 我们来看一下assertReducerShape
函数: 遍历reducers中的reducer,检查reducer是否是符合redux规范的reducer。 使用ActionTypes.INIT和随机type生成的action作为第二个参数,分别进行检验 如果返回的state为undefined,则不符合redux规范。
接下来设置一个flag
叫做hasChanged
,默认是false
。然后遍历reducers
中的所有reducer
,分别计算这些子reducer
,并将其返回的子state
结合成一个大的state
。比较计算出来的子state
与计算之前的子state
是否相同,如果不同,则将hasChanged
设为true
。 hasChanged = hasChanged || nextStateForKey !== previousStateForKey
只要有一个reducer计算出来的state和之前的不一样,就表明状态树改变了
最后,通过判断hasChanged
是否变化,返回nextState
或state
。