博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
逐行阅读redux源码(二)combineReducers
阅读量:6936 次
发布时间:2019-06-27

本文共 9032 字,大约阅读时间需要 30 分钟。

前情提要

认识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 是对指定 typeaction 进行响应,从而对初始的 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值的reduceraction的描述语以及提示。

  • 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,因为当使用storereplaceReducer时会自动触发该内置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)环境下对类型为undefinedreducer进行了过滤和打印警告处理,其后又将符合规范的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需要注意传入的对象每个键必须对应一个类型为functionreducer(废话

请大家记住这几个点,在这些前提下能够帮助你更快的理解我们的combineReducers

感谢你的阅读~

转载地址:http://lybnl.baihongyu.com/

你可能感兴趣的文章
iOS地图选址
查看>>
Shpinx在PHPCMS里的使用及配置
查看>>
Linux Oracle Rac 10G 搭建& Patch
查看>>
Apache与Nginx网络模型对比
查看>>
Java 二重循环实现对象去重
查看>>
[Unity3d]socket通信 切换到web版本时报错SecurityException解决办法
查看>>
修改windows service的启动类型
查看>>
快速构建Windows 8风格应用9-竖直视图
查看>>
Chrome浏览器设置不缓存
查看>>
YII2出现SQLSTATE[HY000] [2002] No such file or director
查看>>
搭建nginx+3*tomcat环境 实现session共享
查看>>
毕业只是开始:你准备好了吗?
查看>>
交互式自动化脚本模板
查看>>
顺丰和菜鸟对用户数据寸土不让 战争平息需监管层
查看>>
软件测试LR通用性能分析流程
查看>>
如何升级phpmyadmin
查看>>
hibernate添加时间问题
查看>>
深入浅出CChart 每日一课——第十三课 似曾相识之云图,乱花渐欲迷人眼
查看>>
Oracle操作的部分ddl语句
查看>>
Excel 中使用SQL 语句查询数据(四)
查看>>