前言

对于react技术栈的前端同学来说,redux应该是相对熟悉的。其代码之精简和设计之巧妙,一直为大家所推崇。此外redux的注释简直完美,阅读起来比较省事。原本也是强行读了通源码,现在也忘得差不多了。因为最近打算对redux进行些操作,所以又开始重读了redux,收益匪浅。

关于redux的基本概念,这里就不再详细描述了。可以参考Redux 中文文档

阅读源码感受

有很多大牛已经提供了很多阅读经验。

个人感觉一开始就强行读源码是不可取的,就像我当初读的第一遍redux,只能说食之无味,现在全忘了。

应该是对其基础用法比较熟练之后,有问题或者有兴趣时再读比较好,结合文档或者实例,完整的流程走一走。

此外直接源码仓库clone下来,本地跑一跑,实在看不懂的断点跟进去。

对于不理解的地方,可能是某些方法不太熟悉,这时候多去找找其具体用法和目的

实在不明白的可以结合网上已有的源码实例,和别人的思路对比一下,看自己哪里理解有偏差。

一句话,希望读过之后对自己有启发,更深入的理解和学习,而非只是说起来读过而已。

redux 提供了如下方法:

export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}

下面的文章就是按照Redux 中文文档例子的顺序,来分别看下各方法的实现。

action和actionCreater

定义和概念

action 本质上是 JavaScript 普通对象

在 Redux 中的 actionCreater就是生成 action 的方法

//addTodo 就是actionCreater
function addTodo(text) {
//return的对象即为action
return {
type: ADD_TODO,
text
}
}

在 传统的 Flux 实现中,当调用 action 创建函数时

一般会触发一个 dispatch

Redux 中只需把 action 创建函数的结果传给 dispatch() 方法即可发起一次 dispatch 过程。

dispatch(addTodo(text))
//或者
const boundAddTodo = text => dispatch(addTodo(text))

当然实际使用的时候,一般情况下(这里指的是简单的同步actionCreater)我们不需要每次都手动dispatch,

react-redux 提供的 connect() 会帮我们来做这个事情。

里面通过bindActionCreators() 可以自动把多个 action 创建函数 绑定到 dispatch() 方法上。

这里先不涉及connect,我们一起看看bindActionCreators如何实现的。

在看之前,我们可以大胆的猜一下,如果是我们要提供一个warper,将两个方法绑定在一起会怎么做:

function a (){
/*.....*/
};
function b(f){
/*.....*/
return f()
}

b里面调用a(先不考虑其他),通过一个c来绑定一下

function c(){
return ()=> b(a)
}

应该就是这么个样子,那么看一下具体实现

bindActionCreators()

先看源码:

// 绑定单个actionCreator
function bindActionCreator(actionCreator, dispatch) {
//将方法dispatch中,避免了action创建手动调用。
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
// function 说明是单个的actionCreator 直接调用bindActionCreator
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// 校验,否则抛错
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(`错误提示`)
}
//获取keys数组,以便遍历
var keys = Object.keys(actionCreators)
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
//依次进行校验绑定操作
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
//返回
return boundActionCreators
}

该方法分为两部分

首先是 bindActionCreator

对单个ActionCreator方法封装

   function bindActionCreator(actionCreator, dispatch) {
//将方法dispatch中,避免了action创建手动调用。
return (...args) => dispatch(actionCreator(...args))
}

bindActionCreators的actionCreators期望是个对象,即actionCreator,

可以想到下面肯定是对该对象进行属性遍历,依次调用bindActionCreator

下面bindActionCreators的动作就是处理该对象

  • typeof actionCreators === 'function'

    单独的方法,直接调用 bindActionCreator结束
  • 如果不是对象或者为null,那么抛错
  • 对于对象,根据key进行遍历,获取包装之后的 boundActionCreators 然后返回
  // function 说明是单个的actionCreator 直接调用bindActionCreator
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// 校验,否则抛错
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(`错误提示`)
}
//获取keys数组,以便遍历
var keys = Object.keys(actionCreators)
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
//依次进行校验绑定操作
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}

这样我们获得了绑定之后的 actionCreators,无需手动调用dispatch(同步的简单情况下)

reducer

action 只是描述了有事发生及提供源数据,具体如何做就需要reducer来处理(详细介绍就略过了)。

在 Redux 应用中,所有的 state 都被保存在一个单一对象中

当reducer处理多个atcion时,显得比较冗长,需要拆分,如下这样:

function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
text: action.text,
completed: false
}
]
})
case TOGGLE_TODO:
return Object.assign({}, state, {
todos: state.todos.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
})
default:
return state
}
}

需要拆分的时候,每个reducer只处理相关部分的state相比于全部state应该更好,

例如:

//reducer1 中
function reducer1(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return state.reducer1.a
//相比较state只是state.reducer1,显然好一点
return state.a
}

每个 reducer 只负责管理全局 state 中它负责的一部分。

每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据

这样需要在主函数里,分别对子reducer的入参进行管理,可以如下面这样:

function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}

当然redux提供了combineReducers()方法

import { combineReducers } from 'redux'

const todoApp = combineReducers({
visibilityFilter,
todos
})

那么我们来看下combineReducers是如何来实现的

combineReducers

还是把完整的代码放上来

export default function combineReducers(reducers) {
// 获取reducer的key 不作处理的话是子reducer的方法名
var reducerKeys = Object.keys(reducers) var finalReducers = {}
// 遍历 构造finalReducers即总的reducer
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
var finalReducerKeys = Object.keys(finalReducers) var sanityError
try {
// 规范校验
assertReducerSanity(finalReducers)
} catch (e) {
sanityError = e
} return function combination(state = {}, action) {
if (sanityError) {
throw sanityError
}
// 警报信息
if (process.env.NODE_ENV !== 'production') {
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action)
if (warningMessage) {
warning(warningMessage)
}
}
/**
* 当有action改变时,
* 遍历finalReducers,执行reducer并赋值给nextState,
* 通过对应key的state是否改变决定返回当前或者nextState
* */
// state改变与否的flag
var hasChanged = false
var nextState = {}
// 依次处理
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
// 获取对应key的state属性
var previousStateForKey = state[key]
// 目的之一,只处理对应key数据
var nextStateForKey = reducer(previousStateForKey, action)
// 不能返回undefined,否则抛错
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 新状态赋给 nextState对象
nextState[key] = nextStateForKey
// 是否改变处理
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 视情况返回state
return hasChanged ? nextState : state
}
}

入参

首先看一下入参:reducers

  • 即需要合并处理的子reducer对象集合。
  • 可以通过import * as reducers来获取
  • tips:

    reducer应该对default情况也进行处理, 当state是undefined或者未定义的action时,也不能返回undefined。

    返回的是一个总reducer,可以调用每个传入方法,并且分别传入相应的state属性。

遍历reducers

既然是个对象集合,肯定要遍历对象,所以前几步就是这么个操作。

// 获取reducer key  目的在于每个子方法处理对应key的state
var reducerKeys = Object.keys(reducers) var finalReducers = {}
// 遍历 构造finalReducers即总的reducer
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
//获取finalReducers 供下面遍历调用
var finalReducerKeys = Object.keys(finalReducers)

然后是规范校验,作为一个框架这是必须的,可以略过

combination

返回一个function

  • 当action被dispatch进来时,该方法主要是分发不同state到对应reducer处理,并返回最新state

  • 先是标识变量:

    // state改变与否的flag
var hasChanged = false
var nextState = {}
  • 进行遍历finalReducers

    保存原来的previousStateForKey

  • 然后分发对应属性给相应reducer进行处理获取nextStateForKey

    先对nextStateForKey 做个校验,因为reducer要求做兼容的,所以不允许undefined的出现,出现就抛错。

    正常的话就nextStateForKey把赋给nextState对应的key

  • 前后两个state做个比较看是否相等,相等的话hasChanged置为true

    遍历结束之后就获得了一个新的state即nextState

for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
// 获取对应key的state属性
var previousStateForKey = state[key]
// 目的之一,只处理对应key数据
var nextStateForKey = reducer(previousStateForKey, action)
// 不能返回undefined,否则抛错
if (typeof nextStateForKey === 'undefined') {
//.....
}
// 新状态赋给 nextState对象
nextState[key] = nextStateForKey
// 是否改变处理
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}

根据hasChanged来决定返回新旧state。

// 视情况返回state
return hasChanged ? nextState : state

到这里combineReducers就结束了。

结束语

这次先分享一半,还是有点多的,剩下的下次再记录一下。抛砖引玉,提升自己,共同学习吧。

参考文章

Redux 中文文档

重读redux源码(一)的更多相关文章

  1. 带着问题看redux源码

    前言 作为前端状态管理器,这个比较跨时代的工具库redux有很多实现和思想值得我们思考.在深入源码之前,我们可以相关注下一些常见问题,这样带着问题去看实现,也能更加清晰的了解. 常见问题 大概看了下主 ...

  2. Redux源码分析之createStore

    接着前面的,我们继续,打开createStore.js, 直接看最后, createStore返回的就是一个带着5个方法的对象. return { dispatch, subscribe, getSt ...

  3. Redux源码分析之applyMiddleware

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  4. Redux源码分析之基本概念

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  5. Redux源码分析之bindActionCreators

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  6. Redux源码分析之combineReducers

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  7. Redux源码分析之compose

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  8. redux源码解读

    react在做大型项目的时候,前端的数据一般会越来越复杂,状态的变化难以跟踪.无法预测,而redux可以很好的结合react使用,保证数据的单向流动,可以很好的管理整个项目的状态,但是具体来说,下面是 ...

  9. Redux源码学习笔记

    https://github.com/reduxjs/redux 版本 4.0.0 先了解一下redux是怎么用的,此处摘抄自阮一峰老师的<Redux 入门教程> // Web 应用是一个 ...

随机推荐

  1. Meshgrid函数的基本用法(转载)

    在Numpy的官方文章里,meshgrid函数的英文描述也显得文绉绉的,理解起来有些难度. 可以这么理解,meshgrid函数用两个坐标轴上的点在平面上画网格. 用法: [X,Y]=meshgrid( ...

  2. unity常用小知识点

    感觉自己抑郁变得更严重了,超级敏感,经常想崩溃大哭,睡眠超差,实在不想药物治疗,多看看书,多约约朋友,多出去走走. 来几句鸡汤吧,人一定要活得明白一点,任何关系都不要不清不楚,说不定最后受伤的就是自个 ...

  3. Python3.x文件处理详解

    Python3.x文件处理详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 任何一门语言都有其特有的操作文件的方式,Python2.x版本有两种操作文件的方式,没错就是open函 ...

  4. null和System.DBNull.Value的区别

    我记得之前在写一个程序的时候用到了这个知识点,当时判断的时候,有时候null可以,有时候必须是System.DBNull.Value 由于不清楚这两个的区别所以纠结了很久.查了一下,二者的区别如下: ...

  5. SQL语句(九)使用特殊关系运算符查询

    使用特殊关系运算符查询 特殊关系运算符 IN.NOT IN IS NULL.IS NOT NULL BETWEEN.NOT BETWEEN LIKE.NOT LIKE IN , NOT IN IN 在 ...

  6. 【转】用CornerStone配置SVN,HTTP及svn简单使用说明

    已经安装了的小伙伴请直接看三步骤 一.下载地址 CornerStoneV2.6:http://pan.baidu.com/s/1qWEsEbM密码:www.macx.cn 二.安装破解方法 1.安装之 ...

  7. CSUST 1506 ZZ的计算器 模拟题

    题目描述:实现一个计算器,可以进行任意步的整数以内的加减乘除运算,运算符号只有+.-.*./,求出结果. 解题报告:一个可以说麻烦的模拟题,我们可以这样,输入以字符串的形式输入,然后将输入先做一遍预处 ...

  8. python实现两个经纬度点之间的距离和方位角

    from:http://blog.csdn.net/zhuqiuhui/article/details/53180395 1.  求两个经纬点的方位角,P0(latA, lonA), P1(latB, ...

  9. Mini Twitter

    Implement a simple twitter. Support the following method: postTweet(user_id, tweet_text). Post a twe ...

  10. gcc __attribute__关键字举例之visibility【转】

    转自:https://blog.csdn.net/starstarstone/article/details/7493144?utm_source=tuicool&utm_medium=ref ...