当前位置:首页>编程日记>正文

Redux源码浅析系列(二):`combineReducer`

本站寻求有缘人接手,详细了解请联系站长QQ1493399855

上一章,我们讲解了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


http://www.coolblog.cn/news/1755fb350e0a9133.html

相关文章:

  • asp多表查询并显示_SpringBoot系列(五):SpringBoot整合Mybatis实现多表关联查询
  • s7day2学习记录
  • 【求锤得锤的故事】Redis锁从面试连环炮聊到神仙打架。
  • 矿Spring入门Demo
  • 拼音怎么写_老师:不会写的字用圈代替,看到孩子试卷,网友:人才
  • Linux 实时流量监测(iptraf中文图解)
  • Win10 + Python + GPU版MXNet + VS2015 + RTools + R配置
  • 美颜
  • shell访问php文件夹,Shell获取某目录下所有文件夹的名称
  • 如何优雅的实现 Spring Boot 接口参数加密解密?
  • LeCun亲授的深度学习入门课:从飞行器的发明到卷积神经网络
  • 支撑微博千亿调用的轻量级RPC框架:Motan
  • Mac原生Terminal快速登录ssh
  • 法拉利虚拟学院2010 服务器,法拉利虚拟学院2010
  • java受保护的数据与_Javascript类定义语法,私有成员、受保护成员、静态成员等介绍...
  • mysql commit 机制_1024MySQL事物提交机制
  • 2019-9
  • jquery 使用小技巧
  • 科学计算工具NumPy(3):ndarray的元素处理
  • vscode pylint 错误_将实际未错误的py库添加到pylint白名单
  • linux批量创建用户和密码
  • 工程师在工作电脑存 64G 不雅文件,被公司开除后索赔 41 万,结果…
  • js常用阻止冒泡事件
  • newinsets用法java_Java XYPlot.setInsets方法代碼示例
  • 气泡图在开源监控工具中的应用效果
  • 各类型土地利用图例_划重点!国土空间总体规划——土地利用
  • php 启动服务器监听
  • dubbo简单示例
  • Ubuntu13.10:[3]如何开启SSH SERVER服务
  • [iptables]Redhat 7.2下使用iptables实现NAT
  • 【设计模式】 模式PK:策略模式VS状态模式
  • CSS小技巧——CSS滚动条美化
  • JS实现-页面数据无限加载
  • 最新DOS大全
  • Django View(视图系统)
  • 阿里巴巴分布式服务框架 Dubbo
  • 阿里大鱼.net core 发送短信
  • Sorenson Capital:值得投资的 5 种 AI 技术
  • 程序员入错行怎么办?
  • 两张超级大表join优化
  • Arm芯片的新革命在缓缓上演
  • 第九天函数
  • Linux软件安装-----apache安装
  • HDU 5988 最小费用流
  • 《看透springmvc源码分析与实践》读书笔记一
  • nagios自写插件—check_file
  • python3 错误 Max retries exceeded with url 解决方法
  • 正式开课!如何学习相机模型与标定?(单目+双目+鱼眼+深度相机)
  • 通过Spark进行ALS离线和Stream实时推荐
  • 行为模式之Template Method模式