Redux 源码初探

Redux 源码初探

在之前的章节当中我们介绍了 Flux 与 ReduxRedux、Flux 和 React-Redux 三者之间的区别,这一章我们就深入的来了解一下 Redux 的源码结构,先来看看 Redux 的源码目录,如下所示

1
2
3
4
5
6
7
8
├── utils/
│ ├── warning.js // 控制台显示警告信息
├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
├── index.js // 入口文件

可以发现,除开 indexwarning 以外,剩余的五个就是 ReduxAPI,下面我们就一个一个来看

compose(…functions)

compose() 方法没有任何依赖,是一个纯函数,它的使用方式是

1
compose(f, g, h)(...arg) => f(g(h(...args)))

不过值得注意的是,它用到了 reduceRight,因此执行顺序是『从右到左』,reduceRight() 方法的功能和 reduce() 功能是一样的,不同的是 reduceRight() 从数组的末尾向前将数组中的数组项做累加,具体实现方式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @param {多个函数,用逗号隔开}
* @return {函数}
*/
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}

if (funcs.length === 1) {
return funcs[0]
}

const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

这里的关键点在于『可传入初始值』,因为 reduce/reduceRight 仅仅是方向的不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var arr = [1, 2, 3, 4, 5]

var re1 = arr.reduce(function (total, i) {
return total + i
})

console.log(re1) // 15

// ==================================

var re2 = arr.reduce(function (total, i) {
return total + i
}, 100)

console.log(re2) // 115

一个比较完整的示例

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
35
36
function func1(num) {
console.log('func1 获得参数 ' + num)
return num + 1
}

function func2(num) {
console.log('func2 获得参数 ' + num)
return num + 2
}

function func3(num) {
console.log('func3 获得参数 ' + num)
return num + 3
}

var re1 = func3(func2(func1(0)))
console.log('re1:' + re1)

console.log('===============')

// 使用 redux 提供的 compose() 方法
var re2 = Redux.compose(func3, func2, func1)(0)
console.log('re2:' + re2)

// ===============================================
// 执行结果如下

// func1 获得参数 0
// func2 获得参数 1
// func3 获得参数 3
// re1:6
// ===============
// func1 获得参数 0
// func2 获得参数 1
// func3 获得参数 3
// re2:6

combineReducers(reducers)

简单来说,这个函数的作用就是通过逐层下分管理对应部分的 state(拆分 state,各个模块管理自己的 state,最后合并),因为在 Flux 中是根据不同的功能拆分出多个 store 分而治之,而 Redux 只允许应用中有唯一的 store,通过拆分出多个 reducer 分别管理对应的 state,无论是 dispatch 哪个 action,都会流通所有的 reducer,这也是为何 reducer 必须返回其对应的 state 的原因(否则整合状态树时,该 reducer 对应的键值就是 undefined

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 combineReducers(reducers) {
var reducerKeys = Object.keys(reducers)
var finalReducers = {}

for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}

var finalReducerKeys = Object.keys(finalReducers)

// 返回合成后的 reducer
return function combination(state = {}, action) {
var hasChanged = false
var nextState = {}
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
var previousStateForKey = state[key] // 获取当前子 state
var nextStateForKey = reducer(previousStateForKey, action) // 执行各子 reducer 中获取子 nextState
nextState[key] = nextStateForKey // 将子 nextState 挂载到对应的键名
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}

bindActionCreators(actionCreators, dispatch)

这个函数主要用于分发 action(比如使用 dispatch(actionCreator())),实现自动 dispatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 为 Action Creator 加装上自动 dispatch 功能
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {

// 类型判断...

var keys = Object.keys(actionCreators)
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
// 逐个装上自动 dispatch 技能
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}

createStore()

源码如下

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'

// Redux 的私有 action 常量
export var ActionTypes = {
INIT: '@@redux/INIT'
}

/**
* @param {函数} reducer 不多解释了
* @param {对象} preloadedState 主要用于前后端同构时的数据同步
* @param {函数} enhancer 可以实现中间件、时间旅行,持久化等
* @return {Store}
*
* ==> Redux 仅提供 applyMiddleware 这个 Store Enhancer
*/
export default function createStore(reducer, preloadedState, enhancer) {

// 省略部分代码,后续会用到
// 这里的代码用于中间件(判断),见后面的 applyMiddleware(...middlewares)

var currentReducer = reducer
var currentState = preloadedState // 这就是整个应用的 state
var currentListeners = [] // 用于存储订阅的回调函数,dispatch 后逐个执行
var nextListeners = currentListeners // 至于为什么使用两个存放回调函数的变量,见下方
var isDispatching = false

/**
* 之所以使用两个存放回调函数的变量
*
* 有这样一种情况,试想在 dispatch 后,回调函数正在被逐个执行(for 循环进行时),假设回调函数队列原本是这样的 [a, b, c, d]
* 现在 for 循环执行到第 3 步,亦即 a、b 已经被执行,准备执行 c,但是此时 a 被取消订阅,那么此时回调函数队列就变成了 [b, c, d],那么第 3 步就对应换成了 d
*
* 为了避免这个问题,本函数会在上述场景中把 currentListeners 复制给 nextListeners,这样的话,dispatch 后,在逐个执行回调函数的过程中
* 如果有新增订阅或取消订阅,都在 nextListeners 中操作,让 currentListeners 中的回调函数得以完整地执行
*
* 既然新增是在 nextListeners 中 push,因此毫无疑问,新的回调函数不会在本次 currentListeners 的循环体中被触发
*
* ===================================================================
*
* 简单总结就是 currentListeners 中的回调函数依次完整地执行,而如果有新增订阅或取消订阅,都在 nextListeners 中操作
*
*/

// 这个函数的作用就是避免发生上述情况 ============ (1)
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}

/**
* 返回 state
*/
function getState() {
return currentState
}

/**
* 这里需要注意的就是,回调函数中如果需要获取 state,请每次都使用 getState() 来获取(而不是开头用一个变量缓存住它)
* 因为回调函数执行期间,有可能有连续几个 dispatch 让 state 改得物是人非,而且别忘了,dispatch 之后,整个 state 是被完全替换掉的
*
* @param {函数} 想要订阅的回调函数
* @return {函数} 取消订阅的函数
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}

var isSubscribed = true

// 避免 (1) 的情况
ensureCanMutateNextListeners()

// 新增订阅在 nextListeners 中操作
nextListeners.push(listener)

// 返回一个取消订阅的函数
return function unsubscribe() {
if (!isSubscribed) {
return
}

isSubscribed = false

// 避免 (1) 的情况
ensureCanMutateNextListeners()
var index = nextListeners.indexOf(listener)

// 取消订阅还是在 nextListeners 中操作
nextListeners.splice(index, 1)
}
}

/**
* 用于改变应用状态(不要直接修改 state,而是 dispatch 一个 action)
*
* 内部的实现是,往 reducer 中传入 currentState 以及 action,用其返回值替换 currentState,最后逐个触发回调函数
* 如果 dispatch 的不是一个对象类型的 action(同步的),而是 Promise/thunk(异步的),则需引入 redux-thunk 等中间件来反转控制权(见下方)
*
* @param & @return {对象} action
*/
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}

if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}

if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}

try {
isDispatching = true
// 关键点,currentState 与 action 会流通到所有的 reducer,所有 reducer 的返回值整合后,替换掉当前的 currentState
// 简单来说,往 reducer 中传入 currentState 以及 action
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}

// 令 currentListeners 等于 nextListeners,表示正在逐个执行回调函数(这就是上面 (1) 中的判定条件)
var listeners = currentListeners = nextListeners

// 逐个触发回调函数
for (var i = 0; i < listeners.length; i++) {
listeners[i]()
}

// 为了方便链式调用,dispatch 执行完毕后,返回 action
return action
}

/**
* 主要用于代码分离按需加载、热替换等情况(这个用的较少)
*
* @param {函数} nextReducer
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}

// 就直接把传入的 nextReducer 赋给 currentReducer
currentReducer = nextReducer

// 触发生成新的 state 树
dispatch({ type: ActionTypes.INIT })
}

/**
* 这是留给 可观察/响应式库 的接口(详情 https://github.com/zenparsing/es-observable)
* 扩展可见 rxjs
* @return {observable}
*/
function observable() { 略 }

// 这里 dispatch 只是为了生成 应用初始状态
dispatch({ type: ActionTypes.INIT })

return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}

反转控制权

所谓的反转控制权即

  • 在同步场景下,dispatch(action) 的这个 action 中的数据是同步获取的,并没有控制权的切换问题
  • 但异步场景下,则需要将 dispatch 传入到回调函数,待异步操作完成后,回调函数自行调用 dispatch(action)

简单来说就是,在异步 Action Creator 中自行调用 dispatch 就相当于反转控制权

它们的作用也仅仅就是把 dispatch 等传入异步 Action Creator 罢了

applyMiddleware(…middlewares)

我们先来简单的看一眼源码,内容不是很多,我们下面会慢慢来进行介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}

我们注意到 applyMiddleware 作为 enhancer 又把 createStore 这个函数作为参数传入并在内部返回函数中调用了,这其实也是依赖注入的理念,然后我们可以发现内部其实将 applyMiddleware 的入参传入的中间件都执行了一次,传参为 getStatedispatch,这里先打住,我们先来看一个中间件的示例,就是一个打印动作前后 state 的中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 一般的写法
function printStateMiddleware(middlewareAPI) { // (1) <== 中间件内可用的 API
return function (dispatch) { // (2) <== 传入上级中间件处理逻辑(若无则为原 store.dispatch)
return function (action) { // (3) <== 整个函数将会被传到下级中间件(如果有的话)作为它的 dispatch 参数
console.log('state before dispatch', middlewareAPI.getState())
var returnValue = dispatch(action) // dispatch 的返回值其实还是 action
console.log('state after dispatch', middlewareAPI.getState())
return returnValue // 将 action 返回给上一个中间件(实际上可以返回任意值,或不返回)
}
}
}

// 比较优雅的写法
const printStateMiddleware = ({ getState }) => next => action => {
console.log('state before dispatch', getState())
let returnValue = next(action)
console.log('state after dispatch', getState())
return returnValue
}

通过上面代码我们可以发现,一般 middleWare 的内部构造都遵从一个 ({ getState, dispatch }) => next => action => {...} 的范式,并且导出的时候已经被调用了一次,即返回了一个需要接收 getStatedispatch 的函数

了解了这一点以后,我们再往后看,接着通过 compose 将中间件高阶组合并增强传入原 store.dispatch 的功能,最后再在返回值内解构覆盖原始 storedispatch,所以这个时候,我们应该已经可以了解 applyMiddleware 到底做了什么,那就是『增强了原始 createStore 返回的 dispatch 的功能』

下面我们就来详细的梳理一下 applyMiddleware 的实现,如下

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 function (createStore) { // 传入 createStore
return function (reducer, preloadedState, enhancer) { // 返回一个函数签名跟 createStore 一模一样的函数,即返回的是一个增强版的 createStore
var store = createStore(reducer, preloadedState, enhancer) // 用原 createStore 先生成一个 store,其包含 getState/dispatch/subscribe/replaceReducer 四个 API
var dispatch = store.dispatch // 指向原 dispatch
var chain = [] // 存储中间件的数组
var middlewareAPI = { // 提供给中间件的 API(其实都是 store 的 API)
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI)) // 给中间件 "组装上" API,见上面的 (1)
dispatch = compose(...chain)(store.dispatch) // 串联所有中间件
return {
...store, // store 的 API 中保留 getState/subsribe/replaceReducer
dispatch // 新 dispatch 覆盖原 dispatch,往后调用 dispatch 就会触发 chain 内的中间件链式串联执行
}
}
}
}

关于上面代码当中的 chain 这里我们多提及一点,例如 chain[M3, M2, M1],而 compose 是从右到左进行包裹的,所以

  • M1dispatch 参数为 store.dispatch
  • M2dispatch 参数为 M1(2)
  • M3dispatch 参数为 M2(2)

最后,我们得到串联后的中间件链 M3(M2(M1(store.dispatch))),这也就是所谓的『中间件的洋葱模型』

以上,经过我们的梳理以后也可以大致了解到,其实最终返回的虽然还是 store 当中的那四个 API,但是其中的 dispatch 函数的功能是已经被增强了的,也就是下图当中所示『中间件提供的是位于 action 被发起之后,到达 reducer 之前的扩展点』

一个综合案例

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function inc() {
return { type: 'INCREMENT' }
}

function dec() {
return { type: 'DECREMENT' }
}

function reducer(state, action) {
state = state || { counter: 0 }

switch (action.type) {
case 'INCREMENT':
return { counter: state.counter + 1 }
case 'DECREMENT':
return { counter: state.counter - 1 }
default:
return state
}
}

// 见上方
function printStateMiddleware(middlewareAPI) {
return function (dispatch) {
return function (action) {
console.log('dispatch 前:', middlewareAPI.getState())
var returnValue = dispatch(action)
console.log('dispatch 后:', middlewareAPI.getState(), '\n')
return returnValue
}
}
}

var enhancedCreateStore = Redux.applyMiddleware(printStateMiddleware)(Redux.createStore)
var store = enhancedCreateStore(reducer)

store.dispatch(inc())
store.dispatch(inc())
store.dispatch(dec())

// ============================================

// 输出结果为
// dispatch 前:{ counter: 0 }
// dispatch 后:{ counter: 1 }

// dispatch 前:{ counter: 1 }
// dispatch 后:{ counter: 2 }

// dispatch 前:{ counter: 2 }
// dispatch 后:{ counter: 1 }

上述的案例中,生成 store 的代码中其实可以将中间件放到 createStore

1
2
3
4
var store = Redux.createStore(
reducer,
Redux.applyMiddleware(printStateMiddleware)
)

如果有多个中间件以及多个增强器,还可以这样写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { createStore, applyMiddleware, compose } from 'redux'

const store = createStore(
reducer,
preloadedState, // 可选参数,前后端同构的数据同步
compose( // 顺序是从右到左
applyMiddleware( // 关乎中间件的增强器,必须置于 compose 执行链的最后
middleware1,
middleware2,
middleware3
),
enhancer3,
enhancer2,
enhancer1
)
)

之所以可以这样使用,是因为在 createStore 的源码的开头部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export default function createStore(reducer, preloadedState, enhancer) {

// ...

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// 只传入 reducer 和 Store Enhancer 这两个参数的情况
enhancer = preloadedState
preloadedState = undefined
}

if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 存在 enhancer 就立即执行,返回增强版的 createStore <== 注 (1)
return enhancer(createStore)(reducer, preloadedState)
}

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

// ...
}

如果有多个中间件以及多个增强器(有多个 enhancer),则注 (1) 中的代码会执行多次,生成最终的超级增强版 store,比如上例中 compose 内部的执行顺序示意图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
原 createStore ————————

return enhancer1(createStore)(reducer, preloadedState, enhancer2)
|
├———————— ——> createStore 增强版 1

return enhancer2(createStore1)(reducer, preloadedState, enhancer3)
|
├———————————> createStore 增强版 1 + 2

return enhancer3(createStore1 + 2)(reducer, preloadedState, applyMiddleware(m1, m2, m3))
|
├————————————————————————————> createStore 增强版 1 + 2 + 3

return appleMiddleware(m1, m2, m3)(createStore1 + 2 + 3)(reducer, preloadedState)
|
├——————————————————————————————————> 生成最终增强版 store
# React

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×