redux源码阅读笔记(一)

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

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

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


redux源码阅读笔记(一)_第1张图片
源码结构

入口文件 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 是如何走下来的呢?

redux源码阅读笔记(一)_第2张图片
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。
    redux源码阅读笔记(一)_第3张图片
    image.png

    从图中可知,函数最终返回一个函数,用于移除当前添加的监听器。
    ensureCanMutateNextListeners 函数内部也较为简单:
    redux源码阅读笔记(一)_第4张图片
    image.png

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

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

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

    redux源码阅读笔记(一)_第6张图片
    image.png

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

    redux源码阅读笔记(一)_第7张图片
    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 有在上文出现过,就不赘述了。

redux源码阅读笔记(一)_第8张图片
image.png

---end ಠ౪ಠ

你可能感兴趣的:(redux源码阅读笔记(一))