文档章节

史上最全的 Redux 源码分析

 乔杰
发布于 2016/12/08 14:21
字数 2023
阅读 12
收藏 1
点赞 0
评论 0

前言

用 React + Redux 已经一段时间了,记得刚开始用Redux 的时候感觉非常绕,总搞不起里面的关系,如果大家用一段时间Redux又看了它的源码话,对你的理解会有很大的帮助。看完后,在回来看Redux,有一种 柳暗花明又一村 的感觉 .

源码

我分析的是用 es6 语法的源码,大家看目录结构,一共有 6 个问件。先说下各个文件大概功能。 输入图片说明

  • applyMiddlewar.js 使用自定义的 middleware 来扩展 Redux
  • bindActionCreators.js 把 action creators 转成拥有同名 keys 的对象,使用时可以直接调用
  • combineReducers.js 一个比较大的应用,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分
  • compose.js 从右到左来组合多个函数,函数编程中常用到
  • createStore.js 创建一个 Redux Store 来放所有的state
  • utils/warnimng.js 控制台输出一个警告,我们可以不用看

我会按每个模块的重要性,去做分析,今天就先把 createStore .js 分享给大家.

createStore.js

注释很详细,直接看注释就可以了

// 导入 lodash ,判断是否是普通(plain)对象
import isPlainObject from 'lodash/isPlainObject'
//导入 symbol 类型的 observable (symbol类型的属性,是对象的私有属性)
import $$observable from 'symbol-observable'

/**
 *定义 Redux Action 的初始化 type
 * 
 */
export var ActionTypes = {
  INIT: '@@redux/INIT'
}

/**
 * 创建一个Redux store来存放应用所有的 state。应用中有且只有一个store
 *
 * @param {Function} reducer 是一个函数,接收两个参数,分别是当前的 state 树和
 * 要处理的 action,返回新的 state 树
 *
 * @param {any} 初始化时的state ,在应用中,你可以把服务端传来经过处理后的 state
 *传给它。如果你使用 combineReducers 创建 reducer,它必须是一个普通对象,与传入
 *的 keys 保持同样的结构。否则,你可以自由传入任何 reducer 可理解的内容。
 *
 * @param {Function} enhancer 是一个组合的高阶函数,返回一个强化过的 store creator .
 *                  这与 middleware相似,它也允许你通过复合函数改变 store 接口。
 *
 * @returns {Store} 返回一个对象,给外部提供 dispatch, getState, subscribe, replaceReducer, 
 */
export default function createStore(reducer, preloadedState, enhancer) {

  //判断 preloadedState 是一个函数并且 enhancer 是未定义 
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState  // 把 preloadedState 赋值给 enhancer
    preloadedState = undefined // 把 preloadedState 赋值为 undefined
  }

  //判断 enhancer 不是 undefined
  if (typeof enhancer !== 'undefined') {
    //判断 enhancer 不是一个函数
    if (typeof enhancer !== 'function') {
      //抛出一个异常 (enhancer 必须是一个函数)
      throw new Error('Expected the enhancer to be a function.')
    }
    //调用 enhancer ,返回一个新强化过的 store creator
    return enhancer(createStore)(reducer, preloadedState)
  }
  
  //判断 reducer 不是一个函数
  if (typeof reducer !== 'function') {
    //抛出一个异常 (reducer 必须是一个函数)
    throw new Error('Expected the reducer to be a function.')
  }

  
  var currentReducer = reducer        //把 reducer 赋值给 currentReducer
  var currentState = preloadedState   //把 preloadedState 赋值给 currentState
  var currentListeners = []           //初始化 listeners 数组
  var nextListeners = currentListeners//nextListeners 和 currentListeners 指向同一个引用
  var isDispatching = false           //标记正在进行dispatch

  /**
   * 保存一份订阅快照
   * @return {void}
   */
  function ensureCanMutateNextListeners() {
    //判断 nextListeners 和 currentListeners 是同一个引用
    if (nextListeners === currentListeners) {
      
      //通过数组的 slice 方法,复制出一个 listeners ,赋值给 nextListeners
      nextListeners = currentListeners.slice()
    }
  }

  /**
   * 获取 store 里的当前 state tree
   *
   * @returns {any} 返回应用中当前的state tree
   */
  function getState() {

    //当前的state tree
    return currentState
  }

  /**
   *
   * 添加一个 listener . 当 dispatch action 的时候执行,这时 sate 已经发生了一些变化,
   * 你可以在 listener 函数调用 getState() 方法获取当前的 state
   *
   * 你可以在 listener 改变的时候调用 dispatch ,要注意
   *
   * 1. 订阅器(subscriptions) 在每次 dispatch() 调用之前都会保存一份快照。
   *    当你在正在调用监听器(listener)的时候订阅(subscribe)或者去掉订阅(unsubscribe),
   *    对当前的 dispatch() 不会有任何影响。但是对于下一次的 dispatch(),无论嵌套与否,
   *    都会使用订阅列表里最近的一次快照。
   *
   * 2. 订阅器不应该关注所有 state 的变化,在订阅器被调用之前,往往由于嵌套的 dispatch()
   *    导致 state 发生多次的改变,我们应该保证所有的监听都注册在 dispath() 之前。
   *
   * @param {Function} 要监听的函数
   * @returns {Function} 一个可以移除监听的函数
   */
  function subscribe(listener) {
    //判断 listener 不是一个函数
    if (typeof listener !== 'function') {

      //抛出一个异常 (listener 必须是一个函数)
      throw new Error('Expected listener to be a function.')
    }

    //标记有订阅的 listener
    var isSubscribed = true

    //保存一份快照
    ensureCanMutateNextListeners()

    //添加一个订阅函数
    nextListeners.push(listener)
    
    //返回一个取消订阅的函数
    return function unsubscribe() {

      //判断没有订阅一个 listener
      if (!isSubscribed) {

        //调用 unsubscribe 方法的时候,直接 return
        return
      }

      //标记现在没有一个订阅的 listener
      isSubscribed = false
      
      //保存一下订阅快照
      ensureCanMutateNextListeners()
      //找到当前的 listener
      var index = nextListeners.indexOf(listener)
      //移除当前的 listener
      nextListeners.splice(index, 1)
    }
  }

  /**
   * dispath action。这是触发 state 变化的惟一途径。
   * 
   * @param {Object} 一个普通(plain)的对象,对象当中必须有 type 属性
   *
   * @returns {Object} 返回 dispatch 的 action
   *
   * 注意: 如果你要用自定义的中间件, 它可能包装 `dispatch()`
   *       返回一些其它东西,如( Promise )
   */
  function dispatch(action) {
    //判断 action 不是普通对象。也就是说该对象由 Object 构造函数创建
    if (!isPlainObject(action)) {

      //抛出一个异常(actions 必须是一个普通对象. 或者用自定义的中间件来处理异步 actions)
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    // 判断 action 对象的 type 属性等于 undefined 
    if (typeof action.type === 'undefined') {

      //抛出一个异常
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }
  
    //判断 dispahch 正在运行,Reducer在处理的时候又要执行 dispatch
    if (isDispatching) {
      
      //抛出一个异常(Reducer在处理的时候不能 dispatch action)
      throw new Error('Reducers may not dispatch actions.')
    }

    try {

      //标记 dispatch 正在运行
      isDispatching = true

      //执行当前 Reducer 函数返回新的 state
      currentState = currentReducer(currentState, action)

    } finally { // (try catch) 最终会被执行的地方

      //标记 dispatch 没有在运行 
      isDispatching = false
    }

    //所有的的监听函数赋值给 listeners
    var listeners = currentListeners = nextListeners

    //遍历所有的监听函数
    for (var i = 0; i < listeners.length; i++) {

      // 执行每一个监听函数
      listeners[i]()
    }

    //返回传入的 action 对象
    return action
  }

  /**
   * 替换计算 state 的 reducer。
   *
   * 这是一个高级 API。
   * 只有在你需要实现代码分隔,而且需要立即加载一些 reducer 的时候才可能会用到它。
   * 在实现 Redux 热加载机制的时候也可能会用到。
   *
   * @param {Function} store 要用的下一个 reducer.
   * @returns {void}
   */
  function replaceReducer(nextReducer) {

    //判断 nextReducer 不是一个函数
    if (typeof nextReducer !== 'function') {

      //抛出一个异常 (nextReducer必须是一个函数)
      throw new Error('Expected the nextReducer to be a function.')
    }

    //当前传入的 nextReducer 赋值给 currentReducer
    currentReducer = nextReducer

    //调用 dispatch 函数,传入默认的 action
    dispatch({ type: ActionTypes.INIT })
  }

  /**
   *  在 creatorStore 内部没有看到此方法的调用
   *  (猜想 : 作者可能想用比较强大,活跃的 observable 库替换现在的 publish subscribe)
   *
   * @returns {observable} 状态改变的时候返回最小的 observable.
   * 想要了解跟多关于 observable 库,建议看下 
   * https://github.com/zenparsing/es-observable (标准 es Observable)
   */
  function observable() {
    //订阅方法赋值给变量 outerSubscribe
    var outerSubscribe = subscribe
    return {
      /**
       * 这是一个最小的观察订阅方法
       * 
       * @param {Object}  观察着的任何一个对象都可以作为一个 observer.
       * 观察者应该有 `next` 方法
       */
      subscribe(observer) {

        //判断 observer 是一个对象
        if (typeof observer !== 'object') {
          //抛出异常
          throw new TypeError('Expected the observer to be an object.')
        }

        //获取观察着的状态
        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        //返回一个取消订阅的方法
        var unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },
      //对象的私有属性,暂时不知道有什么用途
      [$$observable]() {
        return this
      }
    }
  }

  //reducer 返回其初始状态 
  //初始化 store 里的 state tree
  dispatch({ type: ActionTypes.INIT })

  //返回 store 暴漏出的接口
  return {
    dispatch, //唯一一个可以改变 state 的哈按时
    subscribe, //订阅一个状态改变后,要触发的监听函数 
    getState,  // 获取 store 里的 state
    replaceReducer, //Redux热加载的时候可以替换 Reducer
    [$$observable]: observable //对象的私有属性,供内部使用
  }
}

结束语

代码已经放在 githunb 上,大家可以查看 https://github.com/jiechud/redux-source-analyze , 如果对你有帮助,麻烦请 Star 一下吧.

© 著作权归作者所有

共有 人打赏支持
粉丝 0
博文 1
码字总数 2023
作品 0
海淀
前端工程师
从源码理解Redux和Koa2的中间件机制

Redux和Koa的中间件机制相关源码都很精简。 正文我将直接引用部分源码,并加以注释来帮助我们更清晰的理解中间件机制。 Reudx redux的中间件机制在源码中主要涉及两个模块 内部的compose组合...

jiangqizheng ⋅ 05/16 ⋅ 0

redux-logic源码阅读

在用React和Redux做开发时, 都会用到异步的一些东西, 之前更多的用的是或者之类的, 但是都有用的不顺的地方, 有一次突然发现redux-logic是一个很不错的解决方案, 用起来也感觉很顺手, 与市面...

小宋 ⋅ 06/08 ⋅ 0

Redux源码分析--数据中心篇

在如今的前端浪潮中,React和Redux有着举足轻重的地位。React,Redux再加上用于链接他们的代码库就足矣让一些没有足够经验的开发者迷失到代码的海洋里,很容易让程序员们培养成一种别人怎么写...

lanzhiheng ⋅ 06/14 ⋅ 0

Angular2

angular 可重用结构建议 angular 可重用结构建议,非常用价值 掌握 Angular2 的服务 (service) step by step 使用 Angular 构建 Progressive Web Apps(Google 开发者大会演讲 PPT & 视频) ...

掘金官方 ⋅ 01/05 ⋅ 0

理解 redux 中间件

这一小节会讲解 redux 中间件的原理,为下一节讲解 redux 异步 action 做铺垫,主要内容为: Redux 中间件是什么 使用 Redux 中间件 logger 中间件结构分析 applyMiddleware 中间件的执行过程...

陈学家 ⋅ 2016/06/21 ⋅ 0

Redux-thunk快速入门

前言 最近刚刚完成了毕业答辩,我的毕设内容是基于React系列技术栈开发的一个类似Instagram的Web App,戳此看看。开发完后,我惊奇的发现:咦,之前就听说有个叫做redux-thunk的东西,我怎么没...

Guohjia ⋅ 05/22 ⋅ 0

开源,node.js全栈商城源码分享

全栈移动商城(微信端+App) 微信端开发技术:react+redux+router-router+webpack+es6 android 开发技术:react-navtive+redux+es6 ios 开发技术:react-navtive+redux+es6 后台开发技术:k...

wemall微信商城 ⋅ 2017/04/26 ⋅ 2

Redux中间件对闭包的一个巧妙使用

最近在看Redux的源码,发现Redux在使用中间件applyMiddleware.js的源码中,有一个对闭包非常巧妙的使用,解决了“鸡生蛋,蛋生鸡”的问题,特分享给大家。 Redux中间件的函数签名形式如下: ...

苍山沭河 ⋅ 2017/09/11 ⋅ 0

精益 React 学习指南 (Lean React)

本书内容 这本书我会由简单到复杂的带领大家进入 React 的世界, 其中 1 - 3 章节都是 React 的基础知识,需要提醒读者的是大多数的基础知识都可以通过 React 的官方文档学习,如果对英语敏感...

陈学家 ⋅ 2016/05/25 ⋅ 0

实例讲解基于 Flask+React 的全栈开发和部署

简介 我有时在 Web 上浏览信息时,会浏览 Github Trending, Hacker News 和 稀土掘金 等技术社区的资讯或文章,但觉得逐个去看很费时又不灵活。后来我发现国外有一款叫 Panda 的产品,它聚合...

funhacks ⋅ 2017/11/29 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

iExec Blockchain Marketplace for Cloud

iExec Releases the First-Ever Blockchain Marketplace for Trading Cloud Computing Berlin, Germany, May 29, 2018. iExec has released its blockchain-based decentralized cloud marke......

openthings ⋅ 24分钟前 ⋅ 0

OSChina 周二乱弹 —— 加班的代码不要枉费了我的童子功

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @小小编辑:推荐歌曲《29》- 未完成乐队 《29》- 未完成乐队 手机党少年们想听歌,请使劲儿戳(这里) @FalconChen :#看球提醒# 02:00 巴西v...

小小编辑 ⋅ 43分钟前 ⋅ 11

Docker Swarm的前世今生

概述 在我的《Docker Swarm集群初探》一文中,我们实际体验了Docker Swarm容器集群技术的魅力,与《Kubernetes实践录》一文中提到的Kubernetes集群技术相比,Docker Swarm没有Kubernetes显得...

CodeSheep ⋅ 今天 ⋅ 0

骰子游戏代码开源地址

因为阿里云现在服务器已经停用了,所以上面的配置已经失效。 服务端开源地址:https://gitee.com/goalya/chat4.git 客户端开源地址:https://gitee.com/goalya/client4.git 具体运行界面请参考...

算法之名 ⋅ 今天 ⋅ 0

设计模式--装饰者模式

装饰者模式 定义 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。 通用类图 意图 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比...

gaob2001 ⋅ 今天 ⋅ 0

JavaScript零基础入门——(八)JavaScript的数组

JavaScript零基础入门——(八)JavaScript的数组 欢迎大家回到我们的JavaScript零基础入门,上一节课我们讲了有关JavaScript正则表达式的相关知识点,便于大家更好的对字符串进行处理。这一...

JandenMa ⋅ 今天 ⋅ 0

sbt网络问题解决方案

转自:http://dblab.xmu.edu.cn/blog/maven-network-problem/ cd ~/.sbt/launchers/0.13.9unzip -q ./sbt-launch.jar 修改 vi sbt/sbt.boot.properties 增加一个oschina库地址: [reposit......

狐狸老侠 ⋅ 今天 ⋅ 0

大数据,必须掌握的10项顶级安全技术

我们看到越来越多的数据泄漏事故、勒索软件和其他类型的网络攻击,这使得安全成为一个热门话题。 去年,企业IT面临的威胁仍然处于非常高的水平,每天都会看到媒体报道大量数据泄漏事故和攻击...

p柯西 ⋅ 今天 ⋅ 0

Linux下安装配置Hadoop2.7.6

前提 安装jdk 下载 wget http://mirrors.hust.edu.cn/apache/hadoop/common/hadoop-2.7.6/hadoop-2.7.6.tar.gz 解压 配置 vim /etc/profile # 配置java环境变量 export JAVA_HOME=/opt/jdk1......

晨猫 ⋅ 今天 ⋅ 0

crontab工具介绍

crontab crontab 是一个用于设置周期性被执行的任务工具。 周期性执行的任务列表称为Cron Table crontab(选项)(参数) -e:编辑该用户的计时器设置; -l:列出该用户的计时器设置; -r:删除该...

Linux学习笔记 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部