建议:有 redux 的实践后再来看相关的文章。你需要先知道 redux 能让你做什么,才会激起对源码的欲望。
- 推荐看看这篇文章 Redux 卍解,回顾一下 redux都给你提供了哪些 api ,能干些什么。
- 不准备把行行代码都贴出来,建议自行打开源码同步阅读。
redux 的源码内容并不多,可以说很少,相比 koa.js 会多一点 (笑)。源码结构如下图:
入口文件 index.js
入口文件主要做了两件事,
- 判断当前环境是否生产环境。
- 将多个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 是如何走下来的呢?
返回的对象就是反复提到的 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
的类型有 数组,对象和 null
;getPrototypeOf
顾名思义是获取对象的原型,while
循环下 proto
的值是 obj
的顶级原型,函数最终返回值Object.getPrototypeOf(obj) === proto
即 obj
的上一级原型和顶级原型的是否相等的布尔值。我们知道 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。
从图中可知,函数最终返回一个函数,用于移除当前添加的监听器。
ensureCanMutateNextListeners 函数内部也较为简单:
slice
用于获取数组指定区间的浅拷贝,这里没有参数,就是整个数组的一份浅拷贝。这里的用意应该是subscribe
的调用必定会引起 nextListeners 的变化,但是 push 方法不会改变原来变量的内存地址,所以需要手动的 new 一块新的内存来存放变化后的 nextListeners。
卸载监听器的逻辑也比较简单,逻辑也与外部函数类似,就不赘述了,值得注意的是这里是 splice 不是 slice,前者是直接在调用对象上操作的。
注:这里的 isSubscribed 是 subscribe 内生命的变量,初始值为 true。
-
getState
此函数名如其码不过多赘述。
-
replaceReducer
注释直译:用于替换当前 store 计算 state 所使用的reducer。这是一个高级 API。只有在你需要实现代码分隔,而且需要动态加载一些 reducer 的时候才可能会用到它。在实现 Redux 热加载机制的时候也可能会用到。
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 有在上文出现过,就不赘述了。
---end ಠ౪ಠ