一、combineReducers的作用
该方法用来将多个子reducer组合成一个根reducer。之前手写的版本:
代码语言:javascript复制function combineReducers(initialState,reducers){
return function(state=initialState,action){
return Object.entries(reducers).reduce((init,item)=>{
const key = item[0];
const callback = item[1];
init[key] = callback(state[key],action);
return init;
},{});
}
}
对比下官方原本的代码。
二、combineReducers.js源码分析
5行——15行
定义getUndefinedStateErrorMessage()函数,用以返回错误信息。
代码语言:javascript复制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.`
)
}
任何一个子reducer不应该返回undefined,如果没有返回值至少也应该返回null。
99行——178行
返回根reducer函数。
代码语言:javascript复制/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
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 (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]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
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)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
首先,combineReducers()的参数是一个纯对象,对象的key应与初始state树的key一一对应,value就是处理对应的key的相关reducer。
116——131行:
生成3个常量:
- finalReducers:数组类型,存放符合value为函数类型的reducer键值对。
- finalReducerKeys:数组类型,存放key值。
对传入的参数进行遍历整理,finalReducers常量用来存放符合value为函数类型的键值对。
133——143行:
代码语言:javascript复制let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
这里调用了assertReducerShape()函数,函数的作用下边有说明。
shapeAssertionError用于捕获异常信息。
unexpectedKeyCache用于存放那些不存在于preloadedState中的key。
146——160行:
代码语言:javascript复制 if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
getUnexpectedStateShapeWarningMessage()函数的作用参见下边说明。
162——176行:
代码语言:javascript复制 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)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
这块才是combineReducers正真的主体,实现思路和之前手写的一样。唯一不同的就是hasChanged这个变量,如果state没有发生变化则返回上一个previousState,否则返回nextState,虽然reducer无论如何总会执行,但如果本质上state没有改变那么也就没有必然返回一个引用地址不同但值相同的nextState。state是对象,对象是按地址引用的,如果使用了React,这样可以避免一些重复无意义的渲染。
65行——97行
定义assertReducerShape()函数。
代码语言:javascript复制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.`
)
}
})
}
总结起来就是:
- 任何子reducer的返回值都不应该是undefined。
- 如果没有匹配到action.type,则返回初始initialState。
17行——63行
定义getUnexpectedStateShapeWarningMessage()函数。
代码语言:javascript复制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.`
)
}
}
这个函数会在非生产版本下执行,总结起来就是:
- combineReducers的参数不可以是一个空对象。
- 初始preloadedState必须是一个纯对象。
- combineReducers的参数key应该与preloadedState相对应。
三、总结
- combineReducers的参数必须是一个纯对象
- 对象参数的value必须是函数
- 对象参数的key必须与preloadedState的key有对应
- reducer不可以返回undefined