上一章,我们讲解了createStore。下面,我们来看一下combineReducer。 在redux中,我们禁止在应用中创建多个store(我们这里默认讨论的都是客户端应用,同构应用不适用这条规则)。

然而,随着应用变得越来越复杂,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分。

combineReducer就是将一个由多个reducer函数作为valueobject,合并成一个rootReducer。然后就可以对这个reducer调用createStore

下面,我们来具体看一下源码:

import { ActionTypes } from './createStore'
import isPlainObject from 'lodash/isPlainObject'
import warning from './utils/warning'function getUndefinedStateErrorMessage(key, action) {const actionType = action && action.typeconst actionName = (actionType && `"${actionType.toString()}"`) || 'an action'return (`Given action ${actionName}, 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.`)
}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 (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.`)}
}
/*** 遍历reducers中的reducer,检查reducer是否是符合redux规范的reducer。* 使用ActionTypes.INIT和随机type生成的action作为第二个参数,分别进行检验* 如果返回的state为undefined,则不符合redux规范。* @param {Object} reducers */
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.`)}const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')if (typeof reducer(undefined, { type }) === '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.`)}})
}export default function combineReducers(reducers) {//第一次筛选,将reducers中不是function 的键值对给筛选掉。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]}}//第二次筛选,检测`finalReducers`中是否有不符合`redux`规范的`reducer`const finalReducerKeys = Object.keys(finalReducers)let unexpectedKeyCacheif (process.env.NODE_ENV !== 'production') {unexpectedKeyCache = {}}let shapeAssertionErrortry {assertReducerShape(finalReducers)} catch (e) {shapeAssertionError = e}return function combination(state = {}, action) {//如果刚才检测 finalReducers 发现了错误,则抛出错误。if (shapeAssertionError) {throw shapeAssertionError}//如果不是production环境则抛出warningif (process.env.NODE_ENV !== 'production') {const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)if (warningMessage) {warning(warningMessage)}}let hasChanged = falseconst nextState = {}//遍历所有的reducer,分别执行,将其计算出的state组合起来生成一个大的state.// 所以,任何action,redux都会遍历所有的reducer.for (let i = 0; i < finalReducerKeys.length; i++) {const key = finalReducerKeys[i]const reducer = finalReducers[key]//为每一个reducer计算一个state.const previousStateForKey = state[key]const nextStateForKey = reducer(previousStateForKey, action)//如果计算出来的state有undefined,抛出错误.if (typeof nextStateForKey === 'undefined') {const errorMessage = getUndefinedStateErrorMessage(key, action)throw new Error(errorMessage)}//将每一个reducer计算出来的state合并成一个大的state.nextState[key] = nextStateForKey//只要有一个reducer计算出来的state和之前的不一样,就表明状态树改变了。hasChanged = hasChanged || nextStateForKey !== previousStateForKey}return hasChanged ? nextState : state}
}
复制代码

combineReducer的代码其实还是很简单。首先它会经过两次筛选,第一次筛选将reducersvalue值不是function的键值对都剔除掉,第二次筛选将reducers中不符合redux规范的reducer给筛选掉。

那么,什么是符合redux规范的reducer呢? 我们来看一下assertReducerShape函数: 遍历reducers中的reducer,检查reducer是否是符合redux规范的reducer。 使用ActionTypes.INIT和随机type生成的action作为第二个参数,分别进行检验 如果返回的state为undefined,则不符合redux规范。

接下来设置一个flag叫做hasChanged,默认是false。然后遍历reducers中的所有reducer,分别计算这些子reducer,并将其返回的子state结合成一个大的state。比较计算出来的子state与计算之前的子state是否相同,如果不同,则将hasChanged设为truehasChanged = hasChanged || nextStateForKey !== previousStateForKey 只要有一个reducer计算出来的state和之前的不一样,就表明状态树改变了

最后,通过判断hasChanged是否变化,返回nextStatestate

转载于:https://juejin.im/post/5a9d18156fb9a028ce7b185b

Redux源码浅析系列(二):`combineReducer`相关推荐

  1. Redux源码浅析系列(一):`CreateStore`

    使用react+redux开发有一段时间了,刚开始使用并没有深入了解其源码,最近静下心来,阅读了一下,感触颇深. 本系列主要从createStore,combineReducer,compose,ap ...

  2. redux源码分析之二:combineReducers.js

    欢迎关注redux源码分析系列文章: redux源码分析之一:createStore.js redux源码分析之二:combineReducers.js redux源码分析之三:bindActionC ...

  3. Tomcat源码解析系列二:Tomcat总体架构

    Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...

  4. Redux 源码解析系列(一) -- Redux的实现思想

    文章来源: IMweb前端社区 黄qiong(imweb.io) IMweb团队正在招聘啦,简历发至jayccchen@tencent.com Redux 其实是用来帮我们管理状态的一个框架,它暴露给 ...

  5. tio-http-server 源码浅析(二)Http请求的处理HttpRequestHandler

    前言 在上一篇<tio-http-server 源码浅析(一)HttpRequestDecoder的实现>简单分析了HttpRequestDecoder的源码,并且已经得到了HttpReq ...

  6. vue源码分析系列二:$mount()和new Watcher()的执行过程

    续vue源码分析系列一:new Vue的初始化过程 在initMixin()里面调用了$mount() if (vm.$options.el) {vm.$mount(vm.$options.el);/ ...

  7. PhotoSwipe源码解读系列(二)

    作者: 铁锚 日期: 2013年12月19日 说明: 本系列文章为草稿,等待后期完善.源码是jQuery版本的,code.photoswipe-3.0.5.js 1. 代码开头,就是一些版权申明,没什 ...

  8. Antd源码浅析(二)InputNumber组件 一

    前言 上篇我们讲了Icon组件,Icon组件是Antd源码库中实现比较简单的组件,适合大家入门,这篇文章主要和大家一起分析一下数字输入框组件,即InputNumber,难度适中,但蕴含的Antd里较为 ...

  9. Android 4.0 Launcher源码分析系列(二)

    原文:http://mobile.51cto.com/hot-314700.htm 上一节我们研究了Launcher的整体结构,这一节我们看看整个Laucher的入口点,同时Laucher在加载了它的 ...

最新文章

  1. mysql 如何查看constraint定义的的所有约束_MySQL:如何查看表上的所有约束?
  2. 递归解决CSDN论坛上的小朋友分苹果问题
  3. 【Linux】17_计划任务
  4. Springboot文件上传提示:failed to convert java.lang.String to org.springframework.util.unit.DataSize
  5. qt 加载 图片旋转_QT 实现图片旋转的两种方法
  6. spring 注解简单使用
  7. PostgreSQL的clog—从事务回滚速度谈起
  8. 幸亏有这本623页的微服务框架实战笔记,面试篇
  9. java中的greeting_JAVA基础知识——字符串
  10. 蓝桥杯2016年七届C/C++省赛C组第三题-平方怪圈
  11. hadoop开发步骤
  12. Warez 3D动画,超牛
  13. as常用固定搭配_500个英语语法固定搭配
  14. 哈尔滨工业大学计算机科学与技术学院许博文,王轩-哈尔滨工业大学(深圳)计算机科学与技术学院...
  15. 导数的四则运算法则_【数学】求导的方法之四则运算法则
  16. 4.Python复杂数据类型之字典
  17. php实现仿淘票票订票网站
  18. 3D设计为什么要选择实时云渲染技术?
  19. Android逆向之去除APK中的广告
  20. Java将字符串转化为数组

热门文章

  1. 清华姚班2019级新生来了:高考状元、奥赛金牌,也是一批被AI感召的00后
  2. 2、Ktor学习-自动重新加载;
  3. windows driver 分配内存
  4. Ruby错误:iconv will be deprecated in the future, use String#encode instead.
  5. 转载 .net面试题大全(有答案)
  6. Linux升级OpenSSH完整手册
  7. C 语言编程 — 异常处理
  8. 4G EPS 中的随机接入
  9. Kolla 让 OpenStack 部署更贴心
  10. NanoPi NEO Air使用十二:使用自带的fbtft驱动点亮SPI接口TFT屏幕,ST7789V