随着应用越来越复杂,我们会将多个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;
}
};
// 创建根 rootReducer
const rootReducer = combineReducers(_reducers)
// createStore 接受 reducers,创建我们需要的 store。
const store = createStore(rootReducer);
// 拿到组合后的state
const state = store.getState();
rootReducer
返回一个函数 combination(state = {}, action)
,再交给 createStore
来创建我们需要的 store
。
带着示例我们来看源码:
源码
先看辅助函数:
getUndefinedStateErrorMessage,如果一个reducer返回的是一个
undefined
的state,就会调用function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const 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) return
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.`
)
}
}
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]
}
}
// 最终的 Reducers
const finalReducerKeys = Object.keys(finalReducers)
// This is used to make sure we don't warn about the same
// keys multiple times.
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
// 校验所有 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') {
// 非生产环境,校验state
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
// 是否已改变
let hasChanged = false
// state 是否改变的标志位。
const nextState = {}
// 遍历所有reducers
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key] // 上一个state,也就是旧的state
const nextStateForKey = reducer(previousStateForKey, action) // 执行reducer,拿到新的state值
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey // 返回值赋值给nextState
hasChanged = hasChanged || nextStateForKey !== previousStateForKey // 判断state是否发生了变化
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state // state有变化就返回新的state
}
}
总结
combineReducers
函数的作用就是将多个reducer
整合起来,交给createStore
创建一个新的reducers
。
可以这样理解
combineReducers
就是一个高阶的reducer
方法,因为其返回函数combination
方法接收的参数是 state和action,其返回值也是一个 state。
在每次 dispatch
时都会执行 combination
函数遍历所有reducers
,再调用各自的 reducer
。