在咱们开始学习源码以前,咱们不妨先来看看何谓reducers:redux
如图所见,咱们能够明白, reducer
是用来对初始的状态树进行一些处理从而得到一个新的状态树的,咱们能够继续从其使用方法看看 reducer
到底如何作到这一点:api
function reducerDemo(state = {}, action) {
switch (action.type) {
case 'isTest':
return {
isTest: true
};
default:
return state;
}
}
复制代码
从咱们的 reducerDemo
中,咱们能够看到 reducer
接受了两个参数:bash
经过对 action
中的 type
的判断,咱们能够用来肯定当前 reducer
是对指定 type
的 action
进行响应,从而对初始的 state
进行一些修改,得到修改以后的 state
的。从以前咱们在 createStore
中看到的状况:数据结构
currentState = currentReducer(currentState, action)
复制代码
每次 reducer
都会使用上一次的 state
,而后处理以后得到新的 state
。less
可是光是如此的话,在处理大型项目的时候咱们彷佛有点捉襟见肘,由于一个store
只能接受一个reducer
,在大型项目中咱们一般会有很是很是多的 action
用来对状态树进行修改,固然你也能够在 reducer
中声明海量的 switch...case..
来实现对单个action
的响应修改,可是当你这样作的时候,你会发现你的reducer
愈来愈大,处理过程愈来愈复杂,各个业务逻辑之间的耦合度愈来愈高,最后你就会发现这个 reducer
将彻底没法维护。dom
因此为了解决在大型项目中的这类问题,咱们会使用多个reducer
,每一个reducer
会去维护本身所属的单独业务,可是正如咱们以前所说,每一个store
只会接受一个 reducer
,那咱们是如何将reducer一、reducer二、reducer三、reducer4
整合成一个reducer
而且返回咱们所需的状态树的呢?ide
固然咱们能想到的问题,redux
确定也能想到,因此他们提供了 combineReducers
api让咱们能够将多个 reducer
合并成一个 reducer
,并根据对应的一些规则生成完整的状态树,so,让咱们进入正题,开始阅读咱们 combineReducers
的源码吧:函数
首先是combineReducers
的依赖,咱们能在代码的头部找到它:工具
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
复制代码
能够看到,combineReducers
仅仅依赖了以前咱们在上一篇文章中提到的工具类:post
进入正文,在combineReducers
的开始部分,咱们可以发现许多用于返回错误信息的方法:
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.`
)
}
复制代码
从方法可知,这个处理过程当中,咱们传入了key(reducer的方法名)
以及action
对象,以后根据action中是否存在type
得到了action
的描述,最终返回了一段关于出现返回undefined
值的reducer
和action
的描述语以及提示。
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.`
)
}
}
复制代码
在说这段源码以前,咱们须要稍微了解一下,当咱们使用combineReucers
,咱们传入的reducer的数据结构:
function reducer1(state={}, action) {
switch (action.type) {
case 'xxx':
return true;
default:
return state;
}
}
function reducer2() {...}
function reducer3() {...}
function reducer4() {...}
const rootReducer = combineReucers({
reducer1,
reducer2,
reducer3,
reducer4
})
复制代码
咱们传入的时以reducer
的方法名做为键,以其函数做为值的对象,而使用rootReducer
生成的store
会是一样以每一个reducer
的方法名做为键,其reducer
处理以后返回的state
做为值的对象,好比:
// 生成的state
{
reducer1: state1,
reducer2: state2,
reducer3: state3,
reducer4: state4
}
复制代码
至于为什么会这样,咱们后面再提,如今先让咱们继续往下阅读这个生成错误信息的方法。
在这个方法中,其工做流程大概以下:
reducerKeys
获取当前合并的reducer
的全部键值argumentName
获取当前是否为第一次初始化store
的描述reducer
的时候返回抛错信息state
不是一个对象时,返回报错信息。state
上未被reducer
处理的状态的键值unexpectedKeys
,并将其存入cache
值中。replace action
,由于当使用store
的replaceReducer
时会自动触发该内置action
,并将reducer
替换成传入的,此时检测的reducer
和原状态树必然会存在冲突,因此在这种状况下检测到的unexpectedKeys
并不具有参考价值,将不会针对性的返回抛错信息,反之则会返回。经过如上流程,咱们将能对未被reducer
处理的状态进行提示。
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
的初始值和内置的init action
,若是不能返回正确的值(返回了undefined值),那么说明reducer
并无针对默认属性返回正确的值,咱们将提供指定的报错信息。
这以后又使用reducer
处理了undefined
初始值和内置随机action
的状况,这一步的目的是为了排除用户为了不第一步的判断,从而手动针对内置init action
进行处理,若是用户确实作了这种处理,就抛出对应错误信息。
如此,咱们对combineReucers
的错误信息处理已经有了大概的了解,其大体功能以下:
reducer
是不是合规的reducer
不合规reducer
处理的状态了解了这些以后,咱们即可以进入真正的combineReducers
了。
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
}
}
复制代码
首先咱们看到变量声明部分:
接下来,该方法循环遍历了reducerKeys
,并在产品级(production)环境下对类型为undefined
的reducer
进行了过滤和打印警告处理,其后又将符合规范的reducer
放到了finalReducer
中,这一步是为了尽可能减小后面的流程受到空值reducer
的影响。
而后combineReducers
进一步的对这些非空reducer
进行了处理,检测其中是否还有不合规范的reducer
(经过assertReducerShape
),并经过try catch
将这个错误存储到shapeAssertionError
变量中。
正如咱们一直所说,reducer
须要是一个function
,因此咱们的combineReducer
将是一个高阶函数,其会返回一个新的reducer
,也就是源码中的combination
。
在返回的combination
中,会检测是否有shapeAssertionError
,若是有调用该reducer
时将终止当前流程,抛出一个错误,而且在产品级环境下,还会检测是否有未被reducer
处理的state
并打印出来进行提示(不中断流程)。
最后才是整个combination
的核心部分,首先其声明了一个变量来标识当前状态树是否更改,并声明了一个空的对象用来存放接下来会发生改变的状态,而后其遍历了整个finalReducer
,经过每一个reducer
处理当前state
,并将其得到的每一个值和以前状态树中的对应key值得状态值进行对比,若是不一致,那么就更新hasChanged
状态,并将新的状态值放到指定key
值得state
中,且更新整个状态树,固然其中仍是会对出现异常state
返回值的异常处理。
到此,咱们已经通读了combineReducers
中的全部代码,也让咱们稍微对使用combineReducer
时须要注意的几个点作一个总结:
reducer
必需要有非undefined
的返回值reducer
手动去操做内置的action
combineReducers
须要注意传入的对象每一个键必须对应一个类型为function
的reducer
(废话请你们记住这几个点,在这些前提下可以帮助你更快的理解咱们的combineReducers
感谢你的阅读~