读redux源码

Redux源码解读

早就想找个时间看看react全家桶的源码了 这次先从redux看起,后续还会有react-redux和react-router-redux

下面看代码
目录结构如下:

  1. applyMiddleware.js
  2. bindActionCreator.js
  3. combineReducers.js
  4. compose.js
  5. createStore.js
  6. index.js

先看入口文件

index.js

入口文件主要是暴露1-5的核心方法 代码就不贴了

createStore.js

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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// reducer是一个函数, 接收一个单独的reducer或者经过combineRedeucers组合的多个reducer
// preloadedState 是指需要提前加载的state 会和各store的state一起整合到state树里
// enhancer 是store的增强器 下面会重点讲 会涉及到compose和applyMiddleware这两个核心函数
export default function createStore(reducer, preloadedState, enhancer) {
// 如果第二个参数是个函数 并且没有第三个参数 就默认没有穿preloadedState 直接传了增强器函数
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}

// 如果有增强器但是增强器不是函数 抛出异常
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}

// 存在函数类型的增强器就返回执行增强器执行后的结果
// 这里可以推测出增强器的函数结构应该是这样的
// const enhancer = createStore => (reducer, preloadedState, enhancer) => {
// const store = createStore(reducer, preloadedState, enhancer)
// return {
// ...store,
// }
// }
// 所以可以猜测enhancer是为了通过覆盖达到增强store的某一个属性 下面再细说
return enhancer(createStore)(reducer, preloadedState)
}

// 如果reducer不是函数 抛异常
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}

let currentReducer = reducer
// 当前的state树
let currentState = preloadedState
// 这里为什么药声明两个监听队列呢?
// 作者是为了在dispatch的时候能够完整的执行所有的事件监听
// 有人试验过 在一个监听里面取消另一个监听 这个一个无效操作 feature or bug???
let currentListeners = []
let nextListeners = currentListeners
// 是否正在dispatch过程
let isDispatching = false

// 确保nextListeners 和 currentListeners没有引用关系
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
}

// 这个subscribe是为了给currentListener增加监听事件 下面的dispatch函数中 在执行完dispatch操作后 会遍历这个监听队列依次订阅的监听事件
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}

// 保证不能再dispatch的过程中触发监听事件
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#subscribe(listener) for more details.'
)
}

let isSubscribed = true

ensureCanMutateNextListeners()
// 监听事件入栈
nextListeners.push(listener)

// 这种写法还是比较常见且妙的 添加监听事件后 通过闭包返回一个移除当前监听事件的方法
// 用法如下
// const store = createStore(...)
// const subscribe = store.subscribe(() => {})
// 移除只需要执行subscribe()就行了
return function unsubscribe() {
// 如果该监听被取消
if (!isSubscribed) {
return
}
// 无法再dispatch过程中移除监听事件
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#subscribe(listener) for more details.'
)
}
// 监听取消的标志
isSubscribed = false

ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
// 监听事件移除
nextListeners.splice(index, 1)
}
}

// 每个redux middleware都是一个对dispatch的一个增强
// 是redux的dispatch方法 也是页面修改树状态的唯一途径
function dispatch(action) {
// 首先判断action是不是一个纯对象 即通过{}或者new Object()创建
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// 保证每个action必须有type属性
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// 一个action结束才能调用下一个
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}

try {
isDispatching = true
// 这是执行reducer的函数 下面讲到combineReducers的时候再重点讲
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}

// 遍历触发subscribe添加的监听
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}

return action
}

// 替换当前reducer 这个只在热更新上用过
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}

currentReducer = nextReducer
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
}
}
}

// 初始化store
// 收集每个独立store上的state 因为每个store都default返回自身的state
dispatch({ type: ActionTypes.INIT })

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

下面我们看看combineReducers做了什么

combineReducers.js

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
// 看这个文件名字就知道是一个组合多个reducer的方法
// reducers的结构如下
/*
* {
* demo1: function(state, action) {
* switch(action.type) {}
* },
* demo2: function(state, action) {
* switch(action.type) {}
* },
* }
* */
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 (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
// 保证每个reducer对应的value都是函数
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)

let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}

let shapeAssertionError
try {
// 这个方法是为了保证每个自定义reducer至少有一个初始的state和兜底返回值
assertReducerShape(finalReducers)
} catch (e) {s
shapeAssertionError = e
}

// 这一步才是重点
// 返回createStore/dispatch方法中有一行: currentState = currentReducer(currentState, action)
// 所以这个方法才是真正修改状态树的方法
// combination方法会遍历传进来的多个reducers组成的对象 依次执行每个reducer 得到每个reducer相应的state
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}

if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}

// 每次dispatch的都会遍历reducer树让每个小reducer执行这个action
// 保证每次dispatch 都是根据上次的state进行操作的
let hasChanged = false
const nextState = {}
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)
// 该type没有定义 抛异常
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}

看完以上代码我们先举个小例子介绍一下enhancer到底是何方神圣

  1. touch index.js
  2. 开始写demo
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
const { createStore, combineReducers, applyMiddleware, compose } = require('redux')
const initialState = {
count: 1,
}
function count1(state = initialState, action) {
switch (action.type) {
case 'ADD':
return {
...state,
count: state.count + 1,
}
default:
return state
}
}
const store = createStore(count1)
const action1 = { type: 'ADD' }
store.dispatch(action1)
// 我们打印store.getStore()
// 结果是 { count1: { count: 2 } }
/*
* 上面是redux的基本使用 我们主要讲的是store增强器
* 以上也有提到增强器的主要结构
* 我们再做个小demo
* */

// 这是一个每次dispatch前后打印日志的增强器
const loggerEnhancer = createStore => (reducer, proloadedState) => {
const store = createStore(reducer, proloadedState)
// 重写dispatch方法 或者说是在原有的dispatch上包了一层
function dispatch(action) {
console.log(`dispatch action type: ${action.type}`)
const _action = store.dispatch(action)
const nextState = store.getState()
console.log('new state is:', nextState)
// 原来的dispatch就是个纯函数 所以这里的_action就是入参action
return _action
}
return {
...store,
dispatch,
}
}

const storeWithLogger = createStore(count1, loggerEnhancer)
storeWithLogger.dispatch(action1)
// 这样在dispatch的时候就会出现我们的logger内容

// 写到这儿的时候我在想 咦 这玩意儿怎么和redux中间件这么像 肯定compose和applyMiddleware有关系
// 好奇心驱使下 去看了compose和applyMiddleware

接着我们看看compose和applyMiddleware做了什么吧~

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的代码很少 通过reduce方法将各中间件形成一个执行链 前一个中间件套在后一个的外层 并接受后一个函数的执行结果作为自己的入参
举个栗子吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const a = function () {}
const b = function () {}
const c = function () {}
compose([a, b, c])
// 先处理a和b 返回
const ab = function (...args) {
return a(b(...args))
}
// 再处理ab和c 返回
const abc = function (...args) {
return ab(c(...args))
// 这个c(...args)执行结果就是ab的入参
// 所以我们可以换个写法
return a(b(c(...args)))
}
// 换而言之compose的返回值就是 (...args) => a(b(c(...args)))

applyMiddleware.js

我们看看applyMiddleware是怎么实现的 找找他是怎么通过compose组合成enhancer的

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
// 看到这段代码 我觉得猜测是对的 是不是有点像上面的loggerEnhancer的结构 重写dispatch方法
// 所以得出结论 与其说增强器是对store的增强不如说是对dispatch的增强
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// 这里的...args是 reducers、preloadedState、enhancer
const store = createStore(...args)
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)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 这里的compose的作用是包裹所有dispatch操作
dispatch = compose(...chain)(store.dispatch)

return {
...store,
dispatch
}
}
}

我们再写个使用applyMiddleware小demo 通过demo我们再进行设想

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
const { createStore, applyMiddleware, compose } = require('redux')
const initialState = {
count: 1,
}
function count1(state = initialState, action) {
switch (action.type) {
case 'ADD':
return {
...state,
count: state.count + 1,
}
default:
return state
}
}
const store = createStore(count1, applyMiddleware(fn1, fn2))

// 上面两个fn1和fn2我们都没定义 现在我们根据applyMiddleware猜测一下fn1和fn2是啥样子的
// 根据loggerEnhancer的设想
const enhancer = createStore => (reducers, preloadedState, enhancer) => {}
// applyMiddleware也应该是这样的函数体

const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 这行代码可以看出 fn1的雏形 应该是这样的
const fn1 = ({ dispatch, getState }) => {}
// 但是通过compose方法还能接受store.dispatch作为入参 说明fn1应该是这样的
const fn1 = ({ dispatch, getState }) => dispatch => {}
// 通过返回的是一个dispatch 我可以知道fn1原来是这样的
const fn1 = ({ dispatch, getState }) => dispatch => action => {}
// 举个栗子
const a = dispatch => action => {}
const b = dispatch => action => {}
// compose([a, b])的结果为 (...args) => a(b(...args))
// compose([a, b])(store.dispatch) 结果为 a(b(dispatch)) 再做简化就是a(action => {})
// 从这里可以看出 a执行的dispatch其实就是 b(dispatch)的结果
// 如果还有c c执行dispatch就是a(dispatch) 从而达到中间件的作用
// a执行的dispatch就是dispatch


// 举个更确切的栗子
const fn1 = store => next => {
console.log('next1')
return action => {
console.log('fn1')
next(action)
console.log('fn1')
}
}

const fn2 = store => next => {
console.log('next2')
return action => {
console.log('fn2')
next(action)
console.log('fn2')
}
}
/*
* 在执行applyMiddleware时 执行了compose(...chain)(store.sidpatch) 执行顺序是 next2 、 next1 这是中间件入栈的顺序
* 但是调用的顺序不一样 有兴趣可以了解一下洋葱模型
* 执行store.dispatch(action1) 实际打印顺序是 fn1、fn2、fn2、fn1
* fn2在传入store.dispatch的时候 会将
*/
const dispatchFromStore = action => {
console.log('fn2')
next(action)
console.log('fn2')
}
/*
* 当做返回值传给fn1的next
* 那fn1的执行过程就是
* */
const dispatchFromFn2 = action => {
console.log('fn1')
dispatchFromStore(action)
console.log('fn1')
}
// 所以打印顺序是fn1、fn2、fn2、fn1就不奇怪了

bindActionCreators.js

这个文件没有被主函数用到 是被暴露给开发者使用的一个工具函数 用来更方便的组合你的action

createStore/observer方法

这个方法上面没有讲到 也是暴露给开发者的API 我们放到以后讲

小结

这一遍过下来基本就能熟练使用redux的大部分特性了 也感叹代码的精妙 尤其是组合中间件的代码 nb~