写在前面

写React也有段时间了,一直也是用Redux管理数据流,最近正好有时间分析下源码,一方面希望对Redux有一些理论上的认识;另一方面也学习下框架编程的思维方式。

Redux如何管理state

注册store tree

1、Redux通过全局唯一的store对象管理项目中的state

var store = createStore(reducer,initialState);

2、可以通过store注册listener,注册的listener会在store tree每次变更后执行

store.subscribe(function () {
  console.log("state change");
});

如何更新store tree

1、store调用dispatch,通过action把变更的信息传递给reducer

var action = { type: 'add'};
store.dispatch(action);

2、store根据action携带type在reducer中查询变更具体要执行的方法,执行后返回新的state

export default (state = initialState, action)=>{
    switch (action.type) {
        case 'add':
            return {
                count:state.count + 1
            }
            break;
        default:
            break;
    }
}

3、reducer执行后返回的新状态会更新到store tree中,触发由store.subscribe()注册的所有listener

Store实现

主要方法:

  • createStore
  • combineReducers
  • bindActionCreators
  • applyMiddleWare
  • compose

createStore源码分析

查看完整createStore请戳这里

createStore方法用来注册一个store,返回值为包含了若干方法的对象,方法体如下:

export var ActionTypes = {
  INIT: '@@redux/INIT'
}

export default function createStore(){

    function getState(){}

    function dispatch(){}

    function subscribe(){}

    function replaceReducer(){}

    dispatch({ type: ActionTypes.INIT })

    return {
        dispatch,
        subscribe,
        getState,
        replaceReducer
    }
}

下面逐个代码段分析功能

createStore完整函数声明如下:

createStore(
    reducer:(state, action)=>nextState,
    preloadedState:any,
    enhancer:(store)=>nextStore
)=>{
    getState:()=>any,
    subscribe:(listener:()=>any)=>any,
    dispatch:(action:{type:""})=>{type:""},
    replaceReducer:(nextReducer:(state, action)=>nextState)=>void
}

可以看出整个函数是一个闭包结构。参数有三个,返回值公开出若干方法

  • dispatch:分发action
  • subscribe:注册listener,监听state变化
  • getState:读取store tree中所有state
  • replaceReucer:替换reducer,改变state更新逻辑

当然,createStore内部处理了其重载形式,即:可以不传preloadedState

createStore(
    reducer:(state, action)=>nextState,
    enhancer:(store)=>nextStore
)

参数:

  • reducer: reducer必须是一个function类型,此方法根据action.type更新state
  • preloadedState: store tree初始值
  • enhancer: enhancer通过添加middleware,增强store功能

前置操作

进入createSore首先执行如下操作:

  • [40-43] 用于支持两种参数列表形式createStore(reducer,preloadedState,enhancer)createStore(reducer,enhancer)
  • [45-48][53-55] 校验reducer和enhancer的类型(必须为function)

重点分析下50行:

return enhancer(createStore)(reducer, preloadedState)

本语句执行了外部传入的enhancer,接收旧createStore,返回一个新createStore并执行,此过程形成一次递归;

那么递归什么时候停止呢?

可以看到,新createStore执行时,仅有reducer和preloadedState两个参数,再次运行到45行时,不会进入if条件 故不会再形成第二次递归,此时递归停止;

理论上,createStore仅被增强了一次,那如果希望对其进行多次增强该怎么办呢?

Redux提供了composeapplyMiddleWare方法,用来在Store上注册中间件,由此来实现多次增强。

getState()

getState方法比较简单,直接返回当前store tree状态

  • [57-61] 定义了createStore内部要用到的全局变量。其中currentReducercurrentState声明当前reducer方法集合和store tree状态,初始值为外部传入的createStore参数,currentListenersnextListeners定义了存放store变化时要执行响应函数的数组集合

subscribe()

Redux采用了观察者模式,store内部维护listener数组,用于存储所有通过store.subscrib注册的listener,store.subscrib返回unsubscrib方法,用于注销当前listener;当store tree更新后,依次执行数组中的listener

具体代码如下:

dispatch()

dispatch方法主要完成两件事:

1、根据action查询reducer中变更state的方法,更新store tree

2、变更store tree后,依次执行listener中所有响应函数

  • [168-173] 通过currentReducer和action,更新当前的store tree
  • [175-181] 当state tree变更后,依次执行所有注册的listener

有个问题需要注意:

方法中使用了全局定义的isDispatching用于给变更中的store tree加锁;即:只有当本次store tree变更完毕后,才允许执行下一次变更,避免store tree响应多个变更时,结果不同步的问题;但事实上,这种写法也决定了,目前的store tree只能响应同步变更(异步变更需要通过添加中间件实现)

replaceReducer()

replaceReducer用于替换操作store tree中state的方式

整个方法代码量不多,从外部接收新的reducer方法后,替换掉内部旧的ruducer。

需要注意一下199行的dispatch方法,这一行主动触发了一次变更。由于每次dispatch执行后,redux都会执行reducer或子reducer方法(如果使用了combineReducers),所以这一行的作用就是在初始化store tree中所有的state节点。

小结

以上就是整个createStore方法的主要实现过程,其中dispatch方法为控制整个store tree变更的核心方法。触发store tree变更的方式只有一个,就是dispatch一个action

combineReducers源码分析

为什么需要combineReducers

结合上面store tree变更的过程,我们可以看到,真正导致变更的核心代码就是:

currentState = currentReducer(currentState, action)

试想,若整个项目只通过一个reducer方法维护整个store tree,随着项目功能和复杂度的增加,我们需要维护的store tree层级也会越来越深,当我们需要变更一个处于store tree底层的state,reducer中的变更逻辑会十分复杂且臃肿。

combineReducers存在的目的就是解决了整个store tree中state与reducer一对一设置的问题。我们可以根据项目的需要,定义多个子reducer方法,每个子reducer仅维护整个store tree中的一部分state, 通过combineReducers将子reducer合并为一层。这样我们就可以根据实际需要,将整个store tree拆分成更细小的部分,分开维护。

代码实现

combineReducers完整代码请戳这里

整个函数体结构如下:

combineReducers(
    reducers:Object
)=> reducer(
    state:any,
    action:{type:""}
)

参数reducers是一个Object对象,其中包含所有待合并的子reducer方法

返回值是合并后的reducer方法,在执行此方法时,会在已合并的所有子reducer中查询要执行的reducer,并执行,变更其对应的state片段。

下面逐个代码段分析具体实现:

以上这部分主要用于规范化存储子ruducer的reducers对象

  • [103-115] 过滤掉reducers对象中所有非function类型的reducer,合法的结果保存在finalReducers对象中
  • [116-127] 通过assertReducerSanity方法校验所有子reducer的初始值和执行后结果是否为空,是则提示错误。

以上这段代码为combineReducers的核心代码,其返回一个function,用于查询真正要变更的state片段

  • [141-154] 遍历规范化后的finalReducers,获取到当前key对应的子reducer和子state,执行reducer得到当前state片段更新后的状态,并更新到整个store tree中。

    其中129行中的state,应该是整个store tree对应的state,首次获取previousStateForKey时,值可能为undefined,那么接下来执行var nextStateForKey = reducer(previousStateForKey, action)实际上是依次为每个子state片段进行初始化。
  • [153] 本行判定store tree是否被更新,其中nextStateForKey !== previousStateForKey直接通过引用关系判断state是否变更。故一定要注意,定义reducer方法时,一定要遵循函数式编程,确保传入的state与返回的state不要存在引用关系,否则可能导致store tree中状态无法更新。

小结

至此,我们可以看到combineReducers方法,实际就是在每次要执行reducer时,通过action.type定义的类型进行查询,获得子reducer并执行。

通过以上分析,我们需要注意两个问题:

1、子reducer遵循函数式编程,不要直接变更作为参数传入的state,变更state后,一定要返回一个新state对象,不要跟参数state建立引用关系(可以使用Immutable处理state)

2、由于combineReducers内部仅通过action.type作为查询当前要执行的子reducer的依据,会更新所有查询到的state片段,故不建议子reducer中,action.type的值出现重复,否则可能会误更新state。

总结

本篇通过分析源码整理了Redux中Store对象的执行逻辑,重点分析了dispatch(action)后,store tree内部状态如何更新。

篇幅所限,没有分析如何在store上注册中间件,以及如何在store tree变更后,触发页面更新的过程,这些会在之后的博客中更新

第一次写源码分析还有很多不足,如有错误,欢迎指正。

[转载]Redux原理(一):Store实现分析的更多相关文章

  1. OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波

    http://blog.csdn.net/chenyusiyuan/article/details/8710462 OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波 201 ...

  2. 【转】HashMap实现原理及源码分析

    哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景极其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出 ...

  3. 轻松理解Redux原理及工作流程

    轻松理解Redux原理及工作流程 Redux由Dan Abramov在2015年创建的科技术语.是受2014年Facebook的Flux架构以及函数式编程语言Elm启发.很快,Redux因其简单易学体 ...

  4. HashMap实现原理及源码分析之JDK8

    继续上回HashMap的学习 HashMap实现原理及源码分析之JDK7 转载 Java8源码-HashMap  基于JDK8的HashMap源码解析  [jdk1.8]HashMap源码分析 一.H ...

  5. react-router + redux + react-redux 的例子与分析

    一个 react-router + redux  + react-redux 的例子与分析 index.js  import React from 'react' import ReactDom fr ...

  6. 每天学会一点点(HashMap实现原理及源码分析)

    HashMap实现原理及源码分析   哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希 ...

  7. ConcurrentHashMap实现原理及源码分析

    ConcurrentHashMap实现原理 ConcurrentHashMap源码分析 总结 ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对Ha ...

  8. 第2章 rsync算法原理和工作流程分析

    本文通过示例详细分析rsync算法原理和rsync的工作流程,是对rsync官方技术报告和官方推荐文章的解释. 以下是本文的姊妹篇: 1.rsync(一):基本命令和用法 2.rsync(二):ino ...

  9. HashMap和ConcurrentHashMap实现原理及源码分析

    HashMap实现原理及源码分析 哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表, ...

随机推荐

  1. 动态全屏弹窗特效 Morphing Modal Window

    动态变形弹窗特效 Morphing Modal Window 弹出窗体是网页常用的一个交互设计,在这个注重交互动画体验的时代,网页弹窗也可以来点新鲜的点子,比如今天分享的CSS 变形Modal Win ...

  2. overflow hidden 遇上absolute失效

    原文地址 背景 这几天开发的时候遇到了个问题,如图1. 写了个demo 由于页面并没有进行整体缩放,导致在小屏幕手机上显示会有异常.PM要求能够显示最后一个完整的标签. 当在iPhone5手机上查看页 ...

  3. windows 10 删除资源管理器导航栏 Creative Cloud Files

    安装完Adobe的Photoshop.After Effects等软件后,资源管理器导航栏会出现让人讨厌的Creative Cloud Files目录,稍微修改注册表即可删除. 快捷键Windows ...

  4. JS原型和原型链(3)

    构造函数创建对象: function Person() { } var person = new Person(); person.name = 'Kevin'; console.log(person ...

  5. [CSP-S模拟测试]:Walk(树的直径+数学)

    题目描述 给定一棵$n$个节点的树,每条边的长度为$1$,同时有一个权值$w$.定义一条路径的权值为路径上所有边的权值的最大公约数.现在对于任意$i\in [1,n]$,求树上所有长度为$i$的简单路 ...

  6. 无缝轮播的案例 及css3无缝轮播案例

    无缝轮播的案例: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> < ...

  7. ruby异常处理

    begin # 这段代码抛出的异常将被下面的 rescue 子句捕获 rescue # 这个块将捕获所有类型的异常 retry # 这将把控制移到 begin 的开头 end

  8. JS-递归获取当前节点全部指定类型的子节点

    在线预览 方法 使用 nodeType 判断类型,在 allChildNodes 方法内建立递归函数将 allCN 封装在方法内. <!DOCTYPE html> <html lan ...

  9. 【Unity渲染】Camera RenderToCubemap 渲染到立方体纹理

    Unity圣典 传送门:http://www.ceeger.com/Script/Camera/Camera.RenderToCubemap.html Camera.RenderToCubemap 有 ...

  10. std::map使用结构体自定义键值

    使用STL中的map时候,有时候需要使用结构题自定义键值,比如想统计点的坐标出现的次数 struct Node{ int x,y; }; ...... map<Node,int>mp; m ...