内容简介:在我们开始学习源码之前,我们不妨先来看看何谓reducers:如图所见,我们可以明白,从我们的
在我们开始学习源码之前,我们不妨先来看看何谓reducers:
如图所见,我们可以明白, reducer
是用来对初始的状态树进行一些处理从而获得一个新的状态树的,我们可以继续从其使用方法看看 reducer
到底如何做到这一点:
function reducerDemo(state = {}, action) { switch (action.type) { case 'isTest': return { isTest: true }; default: return state; } } 复制代码
从我们的 reducerDemo
中,我们可以看到 reducer
接受了两个参数:
- state
- action
通过对 action
中的 type
的判断,我们可以用来确定当前 reducer
是对指定 type
的 action
进行响应,从而对初始的 state
进行一些修改,获得修改之后的 state
的。从之前我们在 createStore
中看到的情况:
currentState = currentReducer(currentState, action) 复制代码
每次 reducer
都会使用上一次的 state
,然后处理之后获得新的 state
。
但是光是如此的话,在处理大型项目的时候我们似乎有点捉襟见肘,因为一个 store
只能接受一个 reducer
,在大型项目中我们通常会有非常非常多的 action
用来对状态树进行修改,当然你也可以在 reducer
中声明海量的 switch...case..
来实现对单个 action
的响应修改,但是当你这样做的时候,你会发现你的 reducer
越来越大,处理过程越来越复杂,各个业务逻辑之间的耦合度越来越高,最后你就会发现这个 reducer
将完全无法维护。
所以为了解决在大型项目中的这类问题,我们会使用多个 reducer
,每个 reducer
会去维护自己所属的单独业务,但是正如我们之前所说,每个 store
只会接受 一个 reducer
,那我们是如何将 reducer1、reducer2、reducer3、reducer4
整合成一个 reducer
并且返回我们所需的状态树的呢?
combineReducers
当然我们能想到的问题, redux
肯定也能想到,所以他们提供了 combineReducers
api让我们可以将多个 reducer
合并成一个 reducer
,并根据对应的一些规则生成完整的状态树,so,让我们进入正题,开始阅读我们 combineReducers
的源码吧:
依赖
首先是 combineReducers
的依赖,我们能在代码的头部找到它:
import ActionTypes from './utils/actionTypes' import warning from './utils/warning' import isPlainObject from './utils/isPlainObject' 复制代码
可以看到, combineReducers
仅仅依赖了之前我们在上一篇文章中提到的 工具 类:
- ActionTypes(内置的actionType)
- warning(显式打印错误)
- isPlainObject(检测是否为对象)
错误信息处理
进入正文,在 combineReducers
的开始部分,我们能够发现许多用于返回错误信息的方法:
- getUndefinedStateErrorMessage(当reducer返回一个undefined值时返回的错误信息)
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
的描述语以及提示。
- getUnexpectedStateShapeWarningMessage(获取当前state中存在的没有reducer处理的状态的提示信息)
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
处理的状态进行提示。
- assertReducerShape(检测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
了。
合并reducers
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 (reducer在对象中的方法名)
- finalReducers (最终合并生成的的reducers)
接下来,该方法循环遍历了 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
感谢你的阅读~
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【源码阅读】AndPermission源码阅读
- 【源码阅读】Gson源码阅读
- 如何阅读Java源码 ,阅读java的真实体会
- 我的源码阅读之路:redux源码剖析
- JDK源码阅读(六):HashMap源码分析
- 如何阅读源码?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。