redux源码阅读笔记(一)

建议:有 redux 的实践后再来看相关的文章。你需要先知道 redux 能让你做什么,才会激起对源码的欲望。

  • 推荐看看这篇文章 Redux 卍解,回顾一下 redux都给你提供了哪些 api ,能干些什么。
  • 不准备把行行代码都贴出来,建议自行打开源码同步阅读。

redux 的源码内容并不多,可以说很少,相比 koa.js 会多一点 (笑)。源码结构如下图:


源码结构

入口文件 index.js

入口文件主要做了两件事,

  1. 判断当前环境是否生产环境。
  2. 将多个API暴露。

判断生成环境的条件如下;

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
)

process.env.NODE_ENV可能大家都见过但是没有深究,浏览器下是没有 process 这个变量的,只有 node 环境才有, process.env 返回一个包含运行环境参数的对象,但是我没有在文档内看到对 NODE_ENV 这个变量有任何的提及...然后我稍微搜索了一下,这个变量似乎是因为 express 而流行起来的,常见的值有 development, staging, production, testing。
至于在哪里设置,相信朋友们常在 package.json 看见有 script 带着 NODE_ENV=sth这样的参数吧,例如redux源码的打包脚本之一。

  "build:commonjs": "cross-env NODE_ENV=cjs rollup -c -o lib/redux.js",

我们看第二个条件

typeof isCrushed.name === 'string'

isCrushed是一个空函数,官方也有注释,这里声明这个函数就是为了判断函数名是否被压缩,如果被压缩化了且 NODE_ENV 的值不是 production,那就警告用户。

创建 store 对象 createStore.js

读源码之前我们再次熟悉这个 api 用于处理什么事务。回顾一下使用 createStore的地方,

let store = Redux.createStore(aReducer,initState);

emm,接受 reducer 函数 和 一个初始的 state对象作为参数,返回一个 store 对象。回顾一下 reducer 和 state的关系,大概知道 reducer 会对 preState 做一些改动,然后返回 newState,那么肯定会有类似 newState = reducer(preState)这样代码的实现。
我们看看源码:

export default function createStore(reducer, preloadedState, enhancer) {
...

第三个参数 enhancer 官方有注释,这是用于拓展 store 的参数,并不是必须的。有什么作用呢?

if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

嗯,可以预计 enhancer 内部应该是长这样的,最终返回值估计也是 store 对象。

function enhancer (createStore) {
   ...
   return function (reducer,preloadedState){
      ...
   }
}

那没有 enhancer 是如何走下来的呢?

image.png

返回的对象就是反复提到的 store了,其中的 $$observable 是?

import $$observable from 'symbol-observable'

姑且先认为这就是一个普通的 symbol 类型的变量吧。
我们以返回对象的key的陈列顺序翻看对应的函数,

  • dispatch
    redux 约定改变 state 的操作只能是 dispatch 一个 action,而 reducer 是改变 state 的直接函数,可以预计,这个函数内应该还有 state = reducer(action) 之类的操作。源码注释说道,为了方便,dispatch 将返回参数 action,但是这可能被一些第三方中间件所更改。

进入函数后,首当其冲的就是两个判断,符合判断条件就抛出异常。

   if (!isPlainObject(action)) {
     ...

   if (typeof action.type === 'undefined') {
     ...

第二个判断用意很明显,action 是一个必须带有 type 属性的对象。isPlainObject的函数内部如下

export default function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

typeof 的值为 object 的类型有 数组,对象和 nullgetPrototypeOf顾名思义是获取对象的原型,while循环下 proto 的值是 obj 的顶级原型,函数最终返回值Object.getPrototypeOf(obj) === protoobj 的上一级原型和顶级原型的是否相等的布尔值。我们知道 JS 万物皆对象(误),数组的上一级原型是 Array,再上一级才是 Object,也就是说存在多级原型链的对象都会返回 false,比如 Promise。只有家事清白的 obj 才能返回 true。(笑)

再往下是一把锁 if (isDispatching) ,变量是声明在当前函数外createStore 函数内的局部变量(下 currentState,currentReducer 同),默认值为 false,这是场景的锁的设置方式,因为不允许异步操作,逻辑也变得很简单(不禁令人在意如果是异步该如何加锁)。
接着就是 dispatch 主要内容了,这里不赘述了,用意明显。

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

再之后是调用监听器,返回参数 action。dispacth的逻辑的到此结束了。

    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    return action
  • subscribe
    如果你经常是 redeux + react-redux 配合使用,你可能都没用过这个方法(比如我),配合 dispatch 源码和以下 redux 源码截图,可以看出 subscribe 主要是用于管理 listener 。也就是订阅与取消订阅吧,类似 addEventListener。
    image.png

    从图中可知,函数最终返回一个函数,用于移除当前添加的监听器。
    ensureCanMutateNextListeners 函数内部也较为简单:
    image.png

    slice用于获取数组指定区间的浅拷贝,这里没有参数,就是整个数组的一份浅拷贝。这里的用意应该是 subscribe 的调用必定会引起 nextListeners 的变化,但是 push 方法不会改变原来变量的内存地址,所以需要手动的 new 一块新的内存来存放变化后的 nextListeners。
image.png

卸载监听器的逻辑也比较简单,逻辑也与外部函数类似,就不赘述了,值得注意的是这里是 splice 不是 slice,前者是直接在调用对象上操作的。
注:这里的 isSubscribed 是 subscribe 内生命的变量,初始值为 true。

  • getState
    此函数名如其码不过多赘述。

    image.png

  • replaceReducer
    注释直译:用于替换当前 store 计算 state 所使用的reducer。这是一个高级 API。只有在你需要实现代码分隔,而且需要动态加载一些 reducer 的时候才可能会用到它。在实现 Redux 热加载机制的时候也可能会用到。

    image.png

    ActionTypes 是其他模块导入的对象,代码如下,是不是觉得摸不着头脑,可以看看这个

const ActionTypes = {
  INIT:
    '@@redux/INIT' +
    Math.random()
      .toString(36)
      .substring(7)
      .split('')
      .join('.'),
  REPLACE:
    '@@redux/REPLACE' +
    Math.random()
      .toString(36)
      .substring(7)
      .split('')
      .join('.')
}
  • observable
    返回 observable 类对象,{subscribe:fun,[$$observable]:fun}
function observable() {
    //subscribe 是之前提到的函数之一
    const outerSubscribe = subscribe
    return {
      subscribe(observer) {
        if (typeof observer !== 'object') {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        //这里的 outerSubscribe 就是之前提到的 subscribe,该函数执行返回的是卸载订阅器的函数,并赋给了 unsubscribe
        const unsubscribe = outerSubscribe(observeState)
        
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }

这一块还并不能明白有何用意, observer.next 类似于 node 中的 event.emit,当然并不是完全一样,此处如果调用 observable 内返回的 subscribe,就订阅了 state 树的变化事件,你调用 subscribe 时使用的参数,可以做一些对应的操作。

到此, createStore.js 的源码到此就结束了。
然而并没有...,在返回对象之前,函数内部还立即分发了一个 action,这个actionTypes 有在上文出现过,就不赘述了。

image.png

---end ಠ౪ಠ

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容