前后学过好几次redux了,感觉这次终于整明白了,嘿嘿嘿嘿嘿。

基本概念

action

这篇文章解释action函数是:对数据来源做萃取工作的函数。原因是数据来源的多样性,导致数据结构的多样性,action函数就是用来过滤’脏数据’的预处理函数。既可以返回新的数据,也可以只提供线索(只有type,没有payload)。

reducer

这篇文章解释reducer函数是:迎接action函数返回的线索的数据再处理函数reducer只是一个模式匹配,负责调用在其他地方定义好的数据处理函数。

reducer 为什么叫 reducer 呢?因为 action 对象各种各样,每种对应某个 case ,但最后都汇总到 state 对象中,从多到一,这是一个减少( reduce )的过程,所以完成这个过程的函数叫 reducer。


核心函数

combineReducers

这个函数存在的意义是为了state的各个子属性嵌套过深,不方便更新的问题。核心代码(去掉辅助函数)如下:

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
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)

return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
// 此处的思路是每传一个action
// 都会遍历一遍所有的reducer
// 用hasChanged属性表示是否更新
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)

// 因为reducer的default中会返回参数state
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
// 这里返回新的state还是返回原state的判断可以用于react中使用prevProps和this.props进行比较来工作的生命周期函数中
return hasChanged ? nextState : state
}
}

从代码中可以看出,redux的state只能处理一级属性。

参考资料1中,有下面这段叙述:

如果不是返回新 state,只是修改旧 state,我们就很难做到「回退/撤销」以及跟踪全局状态。对比两个数据是否同一,也无法用 ===,而得用 deepEqual 深度遍历来对比值,很耗费性能。

createStore

这个函数存在的意义是协调statereducer以及action这三者。

createStore的返回值是一个对象,这个对象只有方法,没有数据属性,使用JSON.stringify系列化得到的是空对象。而state包含在执行createStore函数时创建的闭包中,通过公有方法getState来获取,拿到的是state的引用。

核心逻辑代码:

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
179
180
function createStore(reducer, preloadedState, enhancer) {
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// enhancer后面再看
return enhancer(createStore)(reducer, preloadedState)
}

// 这里创建了createStore函数的内部变量
// 用于后续创建闭包
// 实际保存state的地方
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false

function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}

// 拿到当前state的引用
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}

return currentState
}

// 订阅执行dispatch之后的回调函数
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}

if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
)
}

// 这里又是一个闭包
// 每订阅一次就会产生一个这样的标记
let isSubscribed = true

ensureCanMutateNextListeners()
nextListeners.push(listener)

return function unsubscribe() {
if (!isSubscribed) {
return
}

if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
)
}

isSubscribed = false

ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
// 这里是为了防止内存泄露
currentListeners = null
}
}

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的引用,改为新的state引用
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}

// 这里执行所有的回调listeners
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}

return action
}

// 这个暂时不管
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}

currentReducer = nextReducer

// This action has a similiar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({ type: ActionTypes.REPLACE })
}

// 这个暂时不管
function observable() {
const outerSubscribe = subscribe
return {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}

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

observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},

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

// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT })

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

bindActionCreators

到这个函数的时,模式从最初的reducer(state, action)通过createStore(reducers, initialState)转换为store.dispatch(action),这一过程从始至终贯彻了函数式编程的思想,从两个参数转换为一个参数。在使用store.dispatch(action)时,面临一个问题,就是action可能是一个’action对象’,也可能是一个’action工程函数’,这就又给store.dispatch(action)的使用带来了问题,bindActionCreators函数的出现就是为了解决这个问题。

下面是这个函数的核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}

export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}

// 。。。

const boundActionCreators = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}

由源码可以看出,使用bindActionCreators可以得到真正具有改变全局state能力的许多函数,剩下的事情,就是将这些函数分发到各个地方,由各个event自主调用即可(引自参考资料1)。

redux的工作流程

  1. 设计全局state的数据结构
  2. 设计更改state数据的actionTypes常量以及其他跟视图展现相关的actionTypes常量
  3. 根据actionTypes常量,书写actionCreator
  4. 根据各个actionCreator的返回值,设计reducer做数据的最后处理
  5. 在有了reducer函数之后,createStore(reducer, initState)得到store对象
  6. 用bindActionCreators函数将actionCreators和store.dispatch绑定起来,得到一组能够修改全局状态的函数
  7. 分发各个状态修改函数到各个DOM事件中

applyMiddlewares

关于这部分,主要参考参考资料3

  1. 中间件基本原理

这里的中间件同样使用了洋葱模型,利用函数式编程中的组合方式,逐层强化dispatch。原理说明见下面的代码(引自参考资料3):

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
let originDispatch = (...args) => {
console.log(...args)
}

const middleware1 = (dispatch) => {
return (...args) => {
console.log('middleware1 before dispatch')
dispatch(...args)
console.log('middleware1 after dispatch')
}
}

const middleware2 = (dispatch) => {
return (...args) => {
console.log('middleware2 before dispatch')
dispatch(...args)
console.log('middleware2 before dispatch')
}
}

originDispatch = middleware2(middleware1(originDispatch))
originDispatch('ruby', 'cool', 'language')

// 运行结果
// middleware2 before dispatch
// middleware1 before dispatch
// ruby cool language
// middleware1 after dispatch
// middleware2 before dispatch
  1. compose的工作过程

下面的代码引自参考资料3

1
2
3
4
5
6
compose(f1, f2, f3, f4)

// 工作过程
// a: f1, b: f2, return: (...args) => f1(f2(...args))
// a: (...args) => f1(f2(...args)), b: f3, return: (...args) => f1(f2(f3(...args)))
// a: (...args) => f1(f2(f3(...args))), b: f4, return: (...args) => f1(f2(f3(f4(...args))))

compose源码:

1
2
3
4
5
6
7
8
9
10
11
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函数在这里的作用就是层层强化dispatch函数。

  1. applyMiddleware源码

这个函数的核心目的就是强化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
function applyMiddleware(...middlewares) {
// 此处的createStore入参为上面提到的createStore
return createStore => (...args) => {
const store = createStore(...args)
// 这里的dispatch形成了一个闭包
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}

const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args) // 在此定义处,dispatch函数即是上面定义的函数,抛出一个错误
}
const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 此处返回的middleware(middlewareAPI)表示的是下面叙述中的第二、三层也就是待传入dispatch返回强化后的dispatch函数
dispatch = compose(...chain)(store.dispatch) // 在中间件真正执行的时候,传入其中的dispatch都是在此处定义的"全量增强"的dispatch。

// 返回的是一个dispatch增强了的store
return {
...store,
dispatch
}
}
}
  1. 一个中间件的示例

下面示例代码出自参考资料3

1
2
3
4
5
6
7
8
9
10
11
12
13
const Logger = (store) => (dispatch) => {
return function(...args) {
const wrappedDispatch = store.dispatch
const getState = store.getState

console.log('before dispatch', getState())
dispatch(...args)
console.log('after dispatch', getState())

console.info(dispatch)
console.info(wrappedDispatch)
}
}

代码中的store入参的结构参考上面middlewareAPI的结构,每个中间件实际是嵌套了三层的函数:

  • 第一层函数:参数有两个,一个是最外层最初定义的dispatch,一个是获取store中数据的getState
  • 第二层函数:参数有一个,是强化了的dispatch,来源是compose函数中的嵌套函数,当前中间件此处的dispatch来自其入参函数,从applyMiddleware源码中可以看到,最后一个中间件函数的入参函数包裹的是redux原生定义的dispatch。这是第二层中的dispatch和第一层中的dispatch的不同
  • 第三层函数:这个函数整体作为强化后的disptach函数,传入其他的中间件中,其参数有一个,可以认为是action,这一点可以从上面中间件原理中看出

参考资料

  1. https://div.io/topic/1309 (applyMiddleware部分没有讲清楚,其他可以)
  2. https://github.com/ecmadao/Coding-Guide/blob/master/Notes/React/Redux/ Redux%E5%85%A5%E5%9D%91%E8%BF%9B%E9%98%B6-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.md#applymiddleware (函数式编程部分可以)
  3. https://www.beansmile.com/blog/posts/redux-apply-middleware-source-code-analysis (专门讲applyMIddleware的,可以)