Redux源码分析(一):主要架构,createStore.js,applyMiddleware.js和compose.js部分

前言:本文采用代码注释和具体说明的形式解读源码。

本文将主要剖析Redux源码的主要架构,createStore.js以及applyMiddleware.js,compose.js的源码剖析,走进Redux的世界,同时对Redux的中间件机制有深入了解。

目录结构

我们首先来看看Redux源码src文件夹当中的目录结构:

1
2
3
4
5
6
7
8
├── utils
├── warning.js
├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
├── index.js

index.js

首先来看index.js,该文件最重要就是将Redux的API暴露给开发者:

1
2
3
4
5
6
7
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}

这边不多赘述,除此之外,index.js还对应用执行环境和Redux是否压缩进行了监测:

1
2
3
4
5
6
7
8
9
10
11
function isCrushed() {}//用于判断压缩与否的函数
if (
typeof process !== 'undefined' &&
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
warning(
//something waring here
)
}

如果当前环境不是生产环境,但是代码被压缩了,那么Redux便会警告开发者。这边Redux使用一个压缩检验函数,通过判断函数isCrushed的函数名是否为isCrushed判断Redux代码是否被压缩。

createStore.js

参数异常判断及初始化

首先我们看到有一个INIT的action:

1
2
3
export const ActionTypes = {
INIT: '@@redux/INIT'
}

这个action主要作用就是用来初始化reducer,使得Redux有一个初始的状态树。在createStore.js的最后部分就会执行dispatch({ type: ActionTypes.INIT })来执行初始化action。

接下来,该文件的核心函数部分共传入了三个参数reducer, preloadedState, enhancer,分别为处理reducer,旧的state和中间件处理参数。

首先就是对传入的参数进行分类处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
//如果preloadedState为函数且enhancer为undefined,那么就说明preloadedState未传入,enhancer实质上就是preloadedState实参
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}//处理异常
return enhancer(createStore)(reducer, preloadedState)//进入中间件执行函数
}
//如果enhancer已定义且enhancer不为函数则抛出错误;如果enhancer已定义且为函数,那么createStore.js就会返回中间执行结果的暴露接口
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
//如果reducer不为函数则抛出错误

接下来进行一系列初始化操作:

1
2
3
4
5
let currentReducer = reducer//当前的reducer
let currentState = preloadedState//当前的State
let currentListeners = []//当前的listeners
let nextListeners = currentListeners//新生成的未来使用的listeners
let isDispatching = false//dispatch的标记锁

subscribe(listener)

subscribe函数在实际开发过程中用到的比较少,主要作用就是绑定监听函数,同时返回一个解除绑定监听的函数,在dispatch内部执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}//确保当前nextListeners和currentListeners相等,同时使得nextListeners变化不影响currentListeners,体现函数式编程思想
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
let isSubscribed = true//subscribe的标记锁
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {//解绑函数
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}

ensureCanMutateNextListeners()函数用来当前的nextListeners和currentListeners变量是一致的,同时因为Redux函数式的编程思维,使用slice函数来返回一个位于不同存储位置的新的listener数组对象,也就是使得nextListeners和currentListeners指向不同的对象。

getState()

getState()主要用来获取当前最新的tate

1
2
3
function getState() {
return currentState
}

replaceReducer(nextReducer)

replaceReducer使用也相对不多,主要目的就是用新的reducer取代当前的reducer:

1
2
3
4
5
6
7
8
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}

dispatch(action)

dispatch的主要作用就是触发reducer以及subscribe绑定的监听函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}//判断action格式
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}//判断action的type变量是否为undefined
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}//判断dispatch是否正在进行
try {
isDispatching = true
currentState = currentReducer(currentState, action)//进行reducer操作
} finally {
isDispatching = false
}
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}//执行监听函数
return action
}

dispatch首先是判断传入的action是否为plain object以及action的type变量是否已赋值,否则action不符合Redux规范将抛出错误。接下来就是对isDispatching进行判断,如果当前isDispatching为true,说明正在执行dispacth;isDispatching为false,则传递currentState和action进行reducer操作,返回一个新的state。

同时执行之前subscribe()的监听函数。

applyMiddleware.js(包括compose.js)

自定义功能的middleware功能使得我们可以通过编写中间件为Redux提供异步交互,日志打印等功能。

首先我们来看applyMiddleware的使用:

中间件使用及格式

1
2
3
4
5
const store = createStore(
reducer,
preloadedState,
applyMiddleware(...middleware)
)

我们首先编写我们需要的中间件,然后通过将middleware传入applyMiddleware即可。

中间件的一般格式如下:

1
2
3
4
5
export default store=>next=>action=>{
console.log('dispatch',action);
next(action);
console.log('finish',action);
}

这是一个简单的柯里化函数,这边使用柯里化实现闭包,可以共享store中的api,同时配合compose可以形成串联的流动。

中间件源码分析

我们看到Redux一般的中间件都要依次传入store,next,action。接下来我们对Redux中间件进行深入分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}//同步Redux的store的接口
chain = middlewares.map(middleware => middleware(middlewareAPI))//对各个个中间件store变量赋值
dispatch = compose(...chain)(store.dispatch)//对各个中间件的next赋值,同时形成中间件串联
return {
...store,
dispatch
}//覆盖原始的dispatch
}
}

applyMiddleware(...middlewares)返回的结果是个函数,该函数将被传入createStore函数执行,我们可以看到在前面我们分析createStore.js内部,如果有enhancer变量,将会执行enhancer(createStore)(reducer, preloadedState)函数,enhancer也就是我们这边applyMiddleware的返回的函数。通过依次传入createStore和reducer,preloadedState变量,我们就进入了中间件的核心部分。

const store = createStore(reducer, preloadedState, enhancer)中我们传入的enhancer为undefined,也就是先执行一遍没有中间件时的返回的store。这时候我们就能获取最原始的store的getState和dispatch:

1
2
3
4
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}

接下来,我们通过执行:chain = middlewares.map(middleware => middleware(middlewareAPI))将我们的middlewareAPI传入我们的中间件数组当中,这时候每一个中间都获得了一个{getState,dispatch}参数,而由于闭包的原因,中间件执行过程中,将一直可以访问到这两个参数,我们就可以在中间件当中获取最新的store以及使用原生的dispatch。获取最新的store在日志打印等中间件中十分重要,而原生的dispatch会在redux-thunk等中间中发挥作用。

这时候我们的中间件已经变成了:(next)=>(action)=>{//可以使用store.getState和store.dispatch}这种形式。

compose函数分析

dispatch = compose(...chain)(store.dispatch)是整个中间件的核心部分,我们首先来看compose函数的实现(compose.js):

1
2
3
4
5
6
7
8
9
10
11
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

compose函数其实跟我们高中数学中的复合函数有着相似性,或者说就是源于复合函数:

1
2
3
f(x)=x+1;
g(x)=2x;
f(g(x))=2x+1;

而我们这边是通过funcs.reduce((a, b) => (...args) => a(b(...args)))来实现相同的效果,例如func=[f,g,h],那么返回的就是(...args) =>f(g(h(...args)))

也就是说compose(…chain)可以将我们的中间件实现层层嵌套,最终形成(...args) =>middleware1(middleware2(middleware3(...args)))的效果。

串联中间件

接下来就是把store.dispatch传入刚才生成的嵌套函数中,我们可以一层层剖析里面的原理:
我们以(...args) =>middleware1(middleware2(middleware3(...args)))为例子,
第一步:当我们把store.dispatch传入middleware3时,这时就把store.dispatch传给了middleware3的next变量,返回一个(action)=>{//next即为store.dispatch}函数;
第二步:接下来就是把(action)=>{//next即为store.dispatch}传给middleware2的中间件的next,这时候中间件middleware2就变成:(action)=>{//next即为(action)=>{//next即为store.dispatch}}就是说,当我们执行middleware2内部的next(action)的时候就是执行(action)=>{//next即为store.dispatch}函数,将会进入middleware3执行;
第三步:接下来就是把middleware2(middleware3(store.dispatch))的结果(action)=>{//next即为(action)=>{//next即为store.dispatch}}传递给middleware1的next变量,这时候middle1就变成(action)=>{//next即为进入middleware2}

也就是说,在compose(...chain)(store.dispatch)当中,主要工作就是:对各个next变量进行赋值,同时使得中间件形成嵌套执行流程。然后我们将compose(...chain)(store.dispatch)赋值给dispatch,从而替换初始的dispatch。store其他的接口仍然不变。

中间件原理及执行流程

中间件总结一下:首先将store.getStatestore.dispatch通过闭包使得所有中间件可以访问;其次,通过compose函数操作,对next进行赋值,使得中间件按顺序依次执行;最后,返回一个dispatch函数接口,可以通过传入action参数,使得中间件按顺序依次执行。

中间件源码大概就是如此,接下来我们看一下具体执行中间件的情景:

当我们stored.dispatch(action)的时候,目前dispatch已经是封装后的dispatch,仍然跟随上面的例子,我们首先将action传入middleware1中,将执行到next(action)出现的地方,这时候middleware1执行暂停,通过next(action)进行middlware2中执行,middleware2也将执行到next(action)出现的地方,进入middleware3,这时候,middle3中的next即为store.dispatch,进入reducer,返回一个新的state。然后我们就执行完middleware3后,继续执行完middleware2,执行完middleware2后继续执行middleware1,到此中间件执行完毕。

在中间件执行过程中,我们可以很方便通过store.getState()获取当前的state,同时也很容易劫持中间件进行异步操作。

结束语

有关Redux源码的主要架构,createStore.js以及applyMiddleware.js,compose.js源码剖析到此为止。