Redux源码分析之combineReducers
combineReducers:把recuder函数们,合并成一个新的reducer函数,dispatch的时候,挨个执行每个reducer
我们依旧先看一下combineReduers的使用效果
let { createStore, bindActionCreators, combineReducers } = self.Redux
//默认state
let todoList = [], couter = 0
// reducer
let todoReducer = function (state = todoList, action) {
switch (action.type) {
case 'add':
return [...state, action.todo]
case 'delete':
return state.filter(todo => todo.id !== action.id)
default:
return state
}
},
couterReducer = function (state = couter, action) {
switch (action.type) {
case 'add':
return ++state
case 'decrease':
return --state
default:
return state
}
}
var reducer = combineReducers({ todoReducer, couterReducer })
//创建store
let store = createStore(reducer)
//订阅
function subscribe1Fn() {
// 输出state
console.log(store.getState())
}
store.subscribe(subscribe1Fn)
// action creater
let actionCreaters = {
add: function (todo) { //添加
return {
type: 'add',
todo
}
}, delete: function (id) {
return {
type: 'delete',
id
}
}
}
let boundActions = bindActionCreators(actionCreaters, store.dispatch)
console.log('todo add')
boundActions.add({
id: 12,
content: '睡觉觉'
})
let boundAdd = bindActionCreators(actionCreaters.add, store.dispatch)
console.log('todo add')
boundAdd({
id: 13,
content: '陪媳妇'
})
let counterActionCreater = {
add: function () {
return {
type: 'add'
}
},
decrease: function () {
return {
type: 'decrease'
}
}
}
let boundCouterActions = bindActionCreators(counterActionCreater, store.dispatch)
console.log('counter add:')
boundCouterActions.add()
console.log('counter decrease:')
boundCouterActions.decrease()
下面是执行结果

我们一起分析一下:
- 执行todo add的时候,看到counterReducer和 todoReducer的数据都有更新,说明两个reducer都执行了。
- 执行counter add的时候,同样两个recuder都执行,但是因为没有参数,加入的是无效数据,这里就提示我们,是不是该进行一些必要的参数判断呢
- 执行counter decrease的时候,同样两个reducer都执行,但是 todoReducer没有tyepe为decrease的action处理函数,当然没有任何产出
我们再回归源码,删除一些判断的代码逻辑,简化后如下:
- 过滤一下reducer,把reducer和key都存起来
- 返回一个新的reducer函数,新的reducer函数执行的时候,便利存起来的reducer,挨个执行
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 (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
return function combination(state = {}, action) {
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)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
这里额外的分析一下,当store的recuder是复合型的时候,怎么初始化state的
createStore传入的第一个参数recuder,是调用 combineReducers 新生成的reducer(依旧是一个函数)
createStore方法返回之前,会这样一下dispatch({ type: ActionTypes.INIT }),disptach的里面我们就关心下面几句
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
也就是执行一下新的reducer,我们继续切换到新的reducer代码里面,同样只关心下面的代码
return function combination(state = {}, action) {
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)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
我们就看我们这个例子 combineReducers({ todoReducer, couterReducer }), 那么上面的key就会是 todoReducer, couterReducer, 那么初始化完毕的state得数据结构就是这样的
{todoReducer:....,couterReducer:......},
有人会想,初始化state,你上次不是用了两种方式,我这里只能说对不起,当你用的是复合型的reducer初始化state的时候,你用第二个参数来初始化state行不通的,
因为为了方便解析代码,上面我是省略了一部分的 ,下面再看看更完整一点的代码(我还是省略了一下)
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 (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
.......
}
这里如果你没通过 aessertRecucerShape检查,是没法进行下去的,我们那看看aessertRecucerShape是个啥玩意,看备注。
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT }) // 传入 undefined,让recuder默认值生效,
if (typeof initialState === 'undefined') { // 如果没有默认值,返回的state就是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') { // 这个主要是防止在recuder你真的自己定义了对type为ActionTypes.INIT处理,创建一个随机的type,测试一下,你应该返回的是有效的state
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.` ) } }) }
这就说明了上述的问题,(-_-)
回顾
1. combineReducers 的参数是一个对象
2. 执行结果返回的依旧是一个reducer
3. 通过combineReducers 返回的reducer创建的store, 再派发某个action的时候,实际上每个内在的reducer都会执行
4. createStrore使用合成的reducer创建的store, 他再派发action返回的是总的大的state
Redux源码分析之combineReducers的更多相关文章
- Redux源码分析之createStore
接着前面的,我们继续,打开createStore.js, 直接看最后, createStore返回的就是一个带着5个方法的对象. return { dispatch, subscribe, getSt ...
- Redux源码分析之applyMiddleware
Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...
- Redux源码分析之基本概念
Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...
- Redux源码分析之bindActionCreators
Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...
- Redux源码分析之compose
Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...
- 史上最全的 Redux 源码分析
前言 用 React + Redux 已经一段时间了,记得刚开始用Redux 的时候感觉非常绕,总搞不起里面的关系,如果大家用一段时间Redux又看了它的源码话,对你的理解会有很大的帮助.看完后,在回 ...
- 正式学习React(四) ----Redux源码分析
今天看了下Redux的源码,竟然出奇的简单,好吧.简单翻译做下笔记: 喜欢的同学自己可以去github上看:点这里 createStore.js import isPlainObject from ' ...
- redux源码学习笔记 - combineReducers
上一篇有了解到,reducer函数的两个为:当前state和此次dispatch的action. state的结构是JavaScript对象,每个key都可以代表着不同意义的数据.比如说 { list ...
- Redux源码学习笔记
https://github.com/reduxjs/redux 版本 4.0.0 先了解一下redux是怎么用的,此处摘抄自阮一峰老师的<Redux 入门教程> // Web 应用是一个 ...
随机推荐
- 原生javascript实现网页显示日期时钟效果
刚接触javascript中Date内置对象时,以为这些方法都太简单了,结果要自己实际操作写一个时钟效果还真一时把我难住了,主要有几点大家要注意的.先看实际效果 要实现这样的效果 某年某月某日星期几几 ...
- centos 6.6 ios镜像文件 下载 官网和阿里云两种方式教你下载
1百度一下:centos 打开打开官网.选择这一项 CET CENTOS 2选择 DVD ISO,双击下载 直接选择左键点击下载 这里需要迅雷 方法 二 打开 https://mirrors.aliy ...
- 2017-06-22初识python
初识python #!/usr/bin/env python (python解释器的文件路径)# -*- coding:utf-8 -*- (使用的编码内型)# python 2.7 <需要加第 ...
- 搭建带热更新功能的本地开发node server
引言 使用webpack有一段时间了,对其中的热更新的大概理解是:对某个模块做了修改,页面只做局部更新而不需要刷新整个页面来进行更新.这样就能节省因为整个页面刷新所产生开销的时间,模块热加载加快了开发 ...
- jquery获得表格可见行的大小数量
alert($("#tableId").find("tbody tr[moban='true']").find(":visible").si ...
- Spring的JDBC(非web程序)的简单例子
第一步: spring配置applicationContext.xml文件,放在src下面: <?xml version="1.0" encoding="UTF-8 ...
- CNN中的卷积核及TensorFlow中卷积的各种实现
声明: 1. 我和每一个应该看这篇博文的人一样,都是初学者,都是小菜鸟,我发布博文只是希望加深学习印象并与大家讨论. 2. 我不确定的地方用了"应该"二字 首先,通俗说一下,CNN ...
- 彩扩机项目--NPN和PNP三极管作为开关管的区别
上图是最终画好的电路.使用的是NPN三极管,并且把NPN三极管放在了下面.下面分析下NPN三极管作为开关管能否放在上面. 从上面两张图分析可知,当三极管作为开关管使用的时候,NPN三极管需要放在下面( ...
- 巧用五招提升Discuz!X运行速度
Discuz!X使用的是数据库应用程序,所以,当数据库的大小.帖子的数目.会员的数目,这些因素都会影响到程序的检索速度,尤其是当论坛的影响力大了,这个问题就更为突出了,虽然,康盛对Discuz进行了更 ...
- pouchdb-find( pouchdb查询扩展插件 ,便于查询)
pouchdb-find pouchdb-find 环境搭建 下载lib bower install pouchdb-find 引入js <script src="pouchdb.js ...