写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。本博客的Github地址。
Redux
的核心思想是:将应用的状态state
存储在唯一的store
中。通过store.dispatch
一个action
来描述触发了什么动作,用reducer
处理相应的action
并返回新的state
。需要注意的是:创建store
的时候需要传入reducer
,真正可以改变应用状态state
的是store.dispatch
API。
相关文件说明
- applyMiddleware.js: 将
middleware
串联起来生成一个更强大的dispatch
函数,这就是中间件的本质作用; - bindActionCreators.js: 把
action creators
转成拥有同名keys
的对象,使用dispatch
把每个action creator
包围起来,使用时可以直接调用; - combineReducers.js: 将多个
reducer
组合起来,每一个reducer
独立管理自己对应的state
; - compose.js: 将
middleware
从右向左依次调用,函数式编程中的常用方法,被applyMiddleware
调用; - createStore.js: 最核心功能,创建一个
store
,包括实现了subscribe,unsubscribe,dispatch及state
的储存; - index.js: 对外export
- utils: 一些小的辅助函数供其他的函数调用 ├── actionTypes.js: redux内置的action,用来初始化initialState ├── isPlainObject.js: 用来判断是否为单纯对象 └── warning.js: 控制台输出一个警告提示
推荐源码的阅读顺序为:
index.js -> creatStore.js -> applyMiddleware.js (compose.js) -> combineReducers.js -> bindActionCreators.js
redux
是一个状态管理框架,是在Flux
的基础上产生的,基本思想是:保证数据的单向流动,同时便于控制、使用、测试。
index.js
index.js
是整个redux
的入口文件,尾部的export
出来的方法是不是都很熟悉,每个方法对应了一个js
文件。
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
function isCrushed() {}
// 如果使用minified(压缩过的)的redux代码会降低性能。
// 这里的isCrushed函数主要是为了验证在非生产环境下的redux代码是否压缩过。如果被压缩了,那么isCrushed.name !== 'isCrushed'为true
// 如果不是production环境且压缩了,给出warning
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' && // 有的浏览器(IE)并不支持Function.name,必须判断先判断是否支持Function.name,才能判断是否minified
isCrushed.name !== 'isCrushed'
) {
warning(
'You are currently using minified code outside of NODE_ENV === "production". ' +
'This means that you are running a slower development build of Redux. ' +
'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
'to ensure you have the correct code for your production build.'
)
}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
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
该代码有两个功能:
- 区分开发环境和生产环境;
- 对外暴露API,相当简洁,常用的API只有五个。
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
__DO_NOT_USE__ActionTypes
,平时在项目里面我们是不太会用的到,redux
的官方文档也没有提到这个,如果我们不看源码,根本不会知道这个东西的存在。里面定义了redux
自带的action
类型,从这个变量的命名来看,这是帮助开发者检查不要使用redux
自带的action
的类型,以防出现错误。
function isCrushed() {}
上述函数主要作用是: 防止开发者在开发环境下对代码进行压缩。开发环境下压缩代码,会降低我们的开发效率,不利于代码调试。下面给出具体的解释:
压缩前:
function isCrushed() {}
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
)
2
3
4
5
6
压缩后:
function d(){}"string"==typeof d.name&&"isCrushed"!==d.name
看到process.env.NODE_ENV
,我们很自然地会跟打包时用的环境变量联系起来。分为以下两种情况进行分析:
- 当
process.env.NODE_ENV !== 'production'
为true
时,即在生产环境下,warning
是不会执行的; - 当
process.env.NODE_ENV !== 'production'
为false
时,即在开发环境下。开发环境下下有可以分成两种情况: 2.1 不压缩代码时:typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed'
不成立,warning
是不会执行的; 2.2 压缩代码时:isCrushed.name === 'string' && isCrushed.name !== 'isCrushed'
是成立的,warning
会执行;
createStore.js
// 导入 symbol 类型的 observable (symbol类型的属性,是对象的私有属性)
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
// 判断是否是简单对象
import isPlainObject from './utils/isPlainObject'
/**
* 创建一个Redux store来存放应用所有的 state。应用中有且只有一个store
*
* @param {Function} reducer 是一个函数,接收两个参数,分别是当前的state树和
* 要处理的action,返回新的state树
*
* @param {any} [preloadedState] 初始化时的state,在应用中,你可以把服务端传来经过处理后的state
* 传给它。如果你使用combineReducers创建reducer,它必须是一个普通对象,与传入
* 的keys保持同样的结构。否则,你可以自由传入任何reducer可理解的内容。
*
* @param {Function} [enhancer] 是一个组合的高阶函数,返回一个强化过的store creator。
* 这与middleware相似,它也允许你通过复合函数改变store接口。
*
* @returns {Store} 返回一个对象,给外部提供 dispatch, getState, subscribe, replaceReducer
*/
export default function createStore(reducer, preloadedState, enhancer) {
// 如果preloadedState和enhancer都是function,不支持,throw new Error
// 我们都知道[initState]为object,[enhancer]为function
// typeof arguments[3] === 'function'为true的话,说明使用了多个store enhancer
// 需要使用compose()函数将多个store enhancer合并成一个函数
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function'
)
}
// 当preloadedState === 'function'和typeof enhancer === 'undefined'都为true时,说明只有两个参数
// 且第二个参数为函数,那么实际上传入的是reducer和enhancer,并没有传入初始化的state(preloadedState)
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState // preloadedState形参中实际保存的是enhancer
preloadedState = undefined
}
// typeof enhancer !== 'undefined'为true,则说明传入了三个参数
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') { // 如果有三个参数且第三个参数不为函数则报错(enhancer必须为函数)
throw new Error('Expected the enhancer to be a function.')
}
// 如果传入了applyMiddleware,applyMiddleware的作用就是将这些enhancer格式化成符合redux要求的enhancer
// 再用处理好的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 // 当前store中的reducer
let currentState = preloadedState // 当前store中存储的状态
let currentListeners = [] // 当前store中放置的监听函数,即当前订阅者列表
let nextListeners = currentListeners // 下一次dispatch时的监听函数列表,即新的订阅列表
// 注意:当我们新添加一个监听函数时,只会在下一次dispatch的时候生效。
let isDispatching = false // 标志是否正在dispatch
// 添加这个函数的意图在下面会讲到,先看代码层面上的作用:
// 如果nextListeners和currentListeners指向同一个对象,即判断两者是不是同一个引用
// 则nextListeners对currentListeners进行浅拷贝
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
// 如果是一个引用的话,浅拷贝出来一个currentListeners赋值给nextListeners
// 其实这里是保存一份订阅快照
// slice不修改原数组,只返回一个浅复制了原数组总的元素的一个新数组
nextListeners = currentListeners.slice()
}
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} store.getState()获取当前应用的state
*/
function getState() {
// 为了保证数据的一致性,当在reducer操作的时候,是不可以读取当前的state值的
if (isDispatching) {
// 参考:https://github.com/reactjs/redux/issues/1568
// 为了保持reducer的pure,禁止在reducer中调用getState
// 纯函数reducer要求根据一定的输入即能得到确定的输出,所以禁止了getState,subscribe,unsubscribe和dispatch等会带来副作用的行为
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.'
)
}
// 返回当前应用的state
// currentState在每次dispatch的时候都会得到相应的更新
return currentState
}
/**
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
* 添加一个监听函数,每当dispatch被调用的时候都会执行这个监听函数
*/
function subscribe(listener) {
// 判断监听者是否为函数。传入的listener必须是一个函数,否则抛出错误
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 判断是否有reducer正在进行数据修改,保证数据的一致性
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 // 设置一个标识,标识该监听器已经订阅了
// 在每次subscribe的时候,浅拷贝一次currentListeners,在nextListener中添加新的listener
ensureCanMutateNextListeners()
// 将listener添加到监听函数数组中
// 需要注意的是:listener在下一次dispatch时才会生效
nextListeners.push(listener)
// subscribe函数返回取消订阅的函数,用于从监听函数数组中删除相应的监听函数
return function unsubscribe() {
// 判断是否已经取消订阅
if (!isSubscribed) { // 如果已经取消过订阅了,则直接返回
return
}
// 判断是否有reducer正在进行数据修改,保证数据的一致性
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 // 设置一个标识,标识该监听器已经取消订阅了
// 在每次unsubscribe的时候,浅拷贝一次currentListeners,在nextListeners取消订阅当前listener
ensureCanMutateNextListeners()
// 找到listener对应的索引
const index = nextListeners.indexOf(listener)
// 从nextListeners中删除unsubscribe的listener
nextListeners.splice(index, 1)
}
}
// 发送action给reducer,返回新的state,并且执行所有添加到store中的监听函数。
function dispatch(action) {
// 判断action是否为简单对象
// action必须是一个plain object,如果想要能处理传进来的函数的话必须使用中间件(redux-thunk等)
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// 判断action.type是否存在,action必须定义type属性
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// 判断当前是否有执行其他的reducer操作
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true // 执行reducer前,将isDispatching设置为true,阻止后续的action进来触发reducer操作
currentState = currentReducer(currentState, action) // 调用reducer,获取到新的state,并赋值给currentState
} finally {
isDispatching = false // 完成之后在finally里将isDispatching再改为false,允许后续的action进来触发reducer操作
}
// 更新监听函数数组
const listeners = (currentListeners = nextListeners)
// 依次执行监听函数数组中的监听函数,一一通知订阅者做数据更新,不传入任何参数
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
// 这个函数是用来替换reducer的
function replaceReducer(nextReducer) {
// 判断所传reducer是否为函数
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
// 通过条件判断之后,将nextReducer赋值给currentReducer,以达到替换reducer效果,并触发state更新操作。
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE }) // 触发相应的action,更新state
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
// 这块代码我们不需要掌握
// 这个observable函数,并没有调用,即便暴露出来我们也没办法使用
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.
// 为啥要有这么一行代码?
// 原因很简单,假设我们没有这样代码,此时currentState就是undefined的,也就我说我们没有默认值了
// 当我们dispatch一个action的时候,就无法在currentState基础上做更新。
// 所以需要拿到所有reducer默认的state,这样后续的dispatch一个action的时候,才可以更新我们的state。
dispatch({ type: ActionTypes.INIT })
// 返回了如下5个方法,其中前3个最为常用
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
函数
createStore
接受三个参数(reducer、preloadedState、enhancer
),reducer和enhancer
我们在项目中用的比较多,preloadedState
用的比较少。reducer
我们很熟悉,主要用来更改整个应用的状态;preloadedState
,它代表初始状态,平时在项目里很少用到它;enhancer
,中文名叫增强器,顾名思义就是来增强redux
的,它的类型的是Function
。
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
2
3
4
5
6
7
这行代码展示了enhancer
的调用过程,根据这个调用过程我们可以推导出enhancer
的函数体的架子应该是这样子的:
function enhancer(createStore) {
return (reducer, preloadedState) => {
//逻辑代码
.......
}
}
2
3
4
5
6
常见的
enhancer
就是redux-thunk
以及redux-saga
,一般都会配合applyMiddleware
一起使用,而applyMiddleware
的作用是:将这些enhancer
格式化成符合redux
要求的enhancer
。具体applyMiddleware
实现,下面我们将会讲到。我们先看redux-thunk
的使用的例子:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
2
3
4
5
6
7
8
看完上面的代码,可能会有人有这么一个疑问:
createStore
函数第二个参数不是preloadedState
吗?这样不会报错吗?首先肯定不会报错,毕竟官方给的例子,不然写个错误的例子也太大跌眼镜了吧!redux
肯定是做了相应的处理的,在createStore.js
可以找到如下的代码:
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
2
3
4
当第二个参数
preloadedState
是Function并且第三个参数enhancer
未定义的时候,说明只传入了两个参数。此时,实际上传入的参数是reducer和enhancer
,并没有传入初始化的state(preloadedState)
。所以,将preloadedState
赋值给enhancer
,然后将preloadedState
置为undefined
的。有了这层处理判断之后,我们就可以大胆地在第二个参数传enhancer
了。
isDispatching作用
let isDispatching = false
变量
isDispatching
,作为锁来用,redux
是一个统一的管理状态容器,它要保证数据的一致性,所以同一个时间里,只能做一次数据修改。如果两个action
同时触发reducer
对同一数据的修改,那么将会带来巨大的灾难。所以变量isDispatching
就是为了防止这一点而存在的。
getState函数
function getState() {
// 为了保证数据的一致性,当在reducer操作的时候,是不可以读取当前的state值的
if (isDispatching) {
// 参考:https://github.com/reactjs/redux/issues/1568
// 为了保持reducer的pure,禁止在reducer中调用getState
// 纯函数reducer要求根据一定的输入即能得到确定的输出,所以禁止了getState,subscribe,unsubscribe和dispatch等会带来副作用的行为
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.'
)
}
// 返回当前应用的state
// currentState在每次dispatch的时候都会得到相应的更新
return currentState
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
需要注意:
store
通过getState
获取到的state
是可以直接被更改的,但是redux
不允许这么做,因为这样不会通知订阅者更新数据。
ensureCanMutateNextListeners解析
// 如果nextListeners和currentListeners指向同一个对象,即判断两者是不是同一个引用
// 则nextListeners对currentListeners进行浅拷贝
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
// 如果是一个引用的话,浅拷贝出来一个currentListeners赋值给nextListeners
// 其实这里是保存一份订阅快照
// slice不修改原数组,只返回一个浅复制了原数组总的元素的一个新数组
nextListeners = currentListeners.slice()
}
}
2
3
4
5
6
7
8
9
10
逻辑很简单,判断nextListeners和currentListeners
是否为同一个引用,还记得dispatch
函数中有这么一句代码:
// Function dispatch
const listeners = (currentListeners = nextListeners)
2
另外定义变量时也有如下代码:
// 定义变量
let currentListeners = []
let nextListeners = currentListeners
2
3
上述两处代码将
nextListeners和currentListeners
引用了同一个数组。而ensureCanMutateNextListeners
就是用来判断这种情况的,当nextListeners和currentListeners
为同一个引用时,则做一层浅拷贝,这里用的是Array.prototype.slice
方法,该方法会返回一个新的数组,这样就可以达到浅拷贝的效果。
函数ensureCanMutateNextListeners
作为处理之后,将新的订阅者加入nextListeners
中,并且返回取消订阅的函数unsubscribe
。
看到这里可能有小伙伴们对currentListeners和nextListeners
有这么一个疑问?函数dispatch
里面将二者合并成一个引用,为啥这里又分开?直接用currentListeners
不可以吗?这里这样做其实也是为了数据的一致性,因为有这么一种的情况存在:当redux
在通知所有订阅者的时候,此时又有一个新的订阅者加进来了。如果只用currentListeners
的话,当新的订阅者插进来的时候,就会打乱原有的顺序,从而引发一些严重的问题。
dispatch({ type: ActionTypes.INIT })
为啥要有这么一行代码?原因很简单,假设我们没有这样代码,此时
currentState
就是undefined
的,也就是说我们没有默认值了,当我们dispatch
一个action
的时候,就无法在currentState
基础上做更新。所以需要拿到所有reducer
默认的state
,这样后续的dispatch
一个action
的时候,才可以更新我们的state
。
applyMiddleware.js
import compose from './compose'
export default function applyMiddleware(...middlewares) { // middlewares是一个中间件数组
// 返回值是一个返回函数的函数(其实就是一个enhancer)
// 外层函数的参数是createStore函数
// 内层函数其实是一个加强版的createStore函数
return createStore => (...args) => {
// 这里是内层函数的函数体
const store = createStore(...args) // 创建一个store
let dispatch = () => { // 定义一个临时的dispatch,如果在中间件构造过程中调用dispatch,会抛出错误信息
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
// 定义middlewareAPI,包含两个方法,一个是getState,另一个是dispatch
// 传给middleware的参数
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 执行所有的middleware
// middlewares调用Array.prototype.map进行改造,存放在chain
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 用compose整合chain数组,并赋值给dispatch,重新改写dispatch
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch // 将新的dispatch替换原先的store.dispatch
}
}
}
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
具体实现逻辑:
- 通过
createStore
方法创建出一个store
; - 定一个
dispatch
,如果在中间件构造过程中调用,抛出错误提示; - 定义
middlewareAPI
,有两个方法,一个是getState
,另一个是dispatch
,将其作为中间件调用的store
的桥接; middlewares
调用Array.prototype.map
进行改造,存放在chain
;- 用
compose
整合chain
数组,并赋值给dispatch
; - 将新的
dispatch
替换原先的store.dispatch
。
前面我们讲enhancer
的时候,提到过这个applyMiddleware
,现在我们将二者的格式对比看一下。
// enhancer
function enhancer(createStore) {
return (reducer,preloadedState) => {
//逻辑代码
.......
}
}
//applyMiddleware
function //applyMiddleware(...middlewares) {
return createStore => (...args) => {
//逻辑代码
.......
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
通过二者的对比,我们发现函数applyMiddleware
的返回值其实就是一个enhancer
。
看完上述的分析,大家可能还有很多疑问(比如刚刚读完的我)。变量
chain
是什么?dispatch
是如何被改造的?下面我们以redux-thunk
为例,模拟一下整个过程。redux-thunk的源码如下:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
2
3
4
5
6
7
8
9
10
11
12
13
14
我们最终得到的thunk是
createThunkMiddleware()
处理过的thunk
,再看createThunkMiddleware
这个函数,返回的是一个返回函数的函数。
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
2
3
4
5
6
7
8
9
我们将上述代码编译成ES5的代码:
function createThunkMiddleware(extraArgument) {
return function({ dispatch, getState }) {
// 这里返回的函数就是chain
return function(next) {
// 这里返回的函数就是改写的dispatch
return function(action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
到这里我们就知道变量
chain
就是以next
作为形参的匿名函数。compose
函数作用是:将多个函数连接起来,将一个函数的返回值作为另一个函数的传参进行计算,得出最终的返回值。这里chain
只是一个函数,所以很简单,就是执行chain
,并将store.dispatch
作为实参传递给next
。
改造后的
dispatch
最终变为:
function(action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// next为之前传入的store.dispatch,即改写前的dispatch
return next(action);
};
2
3
4
5
6
7
如果传入的action是函数,则执行函数;否则直接
dispatch(action)
。
compose.js
compose.js
函数主要作用是:将多个函数连接起来,将一个函数的返回值作为另一个函数的传参进行计算,得出最终的返回值。以烹饪为例,每道菜都是从最初的食材经过一道又一道的工序处理才得到的。compose
的用处就可以将这些烹饪工序连接到一起,我们只需要提供食材,它会自动帮你经过一道又一道的工序处理,烹饪出这道菜。
源码如下:
export default function compose(...funcs) {
// 当funcs长度为0时,返回一个传入什么就返回什么的函数
if (funcs.length === 0) {
return arg => arg
}
// 当funcs长度为1时,返回funcs中的第一项对应的函数
if (funcs.length === 1) {
return funcs[0]
}
// 当funcs长度大于1时,调用Array.prototype.reduce方法进行整合
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
2
3
4
5
6
7
8
9
10
11
12
这里的compose有个特点,他不是从左到右执行的,而是从右到左执行的,下面我们看个例子:
const value = compose(function(value) {
return value + 1;
}, function(value) {
return value * 2;
}, function(value) {
return value - 3;
})(2);
console.log(value); // (2-3)*2+1=-1
2
3
4
5
6
7
8
如果想要其从左向右执行也很简单,做一下顺序的颠倒即可。
===> 转换前 return a(b.apply(undefined, arguments));
===> 转换后 return b(a.apply(undefined, arguments));
2
源码对应的
es5
版本:
function compose() {
var _len = arguments.length;
var funcs = [];
for (var i = 0; i < _len; i++) {
funcs[i] = arguments[i];
}
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce(function (a, b) {
return function () {
return a(b.apply(undefined, arguments));
};
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
梳理一下整个流程,大致如下几步:
- 新建一个新数组
funcs
,将arguments
里面的每一项一一拷贝到funcs
中去; - 当
funcs
的长度为0
时,返回一个传入什么就返回什么的函数; - 当
funcs
的长度为1
时,返回funcs
第0
项对应的函数; - 当
funcs
的长度大于1
时,调用Array.prototype.reduce
方法进行整合。
Array.prototype.reduce()方法
reduce()
方法对累计器和数组中的每个元素(从左到右)应用一个函数,将其简化为单个值。
reducer 函数接收4个参数:
- Accumulator (acc) (累计器);
- Current Value (cur) (当前值);
- Current Index (idx) (当前索引);
- Source Array (src) (源数组)。
reducer
函数的返回值分配给累计器,其值在数组的每个迭代中被记住,并最后成为最终的单个结果值。
语法如下:
arr.reduce(callback, [initialValue])
参数:
callback
callback
执行数组中每个值的函数,包含四个参数:
- accumulator: 累计器累计回调的返回值;它是上一次调用回调时返回的累积值,或initialValue;
- currentValue: 数组中正在处理的元素;
- currentIndex(可选): 数组中正在处理的当前元素的索引。如果提供了
initialValue
,则起始索引号为0
,否则为1
; - array(可选): 调用
reduce()
的数组。
initialValue(可选)
作为第一次调用callback
函数时的第一个参数的值。如果没有提供初始值,则将使用数组中的第一个元素。在没有初始值的空数组上调用reduce
将报错。
函数说明如下
回调函数第一次执行时,accumulator和currentValue
的取值有两种情况:
- 如果调用
reduce()
时提供了initialValue
,accumulator
取值为initialValue
,currentValue
取数组中的第一个值; - 如果没有提供
initialValue
,那么accumulator
取数组中的第一个值,currentValue
取数组中的第二个值。
注意:如果没有提供
initialValue
,reduce
会从索引1
的地方开始执行callback
方法,跳过第一个索引。如果提供initialValue
,从索引0
开始。
如果数组为空且没有提供initialValue
,会抛出TypeError
。如果数组仅有一个元素(无论位置如何)并且没有提供initialValue
,或者有提供initialValue
但是数组为空,那么此唯一值将被返回并且callback
不会被执行。
reduce如何运行
没有提供
initialValue
:
[0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => {
return accumulator + currentValue;
});
2
3
由上图可以看出:没有提供
initialValue
,那么accumulator
取数组中的第一个值,currentValue
取数组中的第二个值,currentIndex
为1。
提供
initialValue
:10
[0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => {
return accumulator + currentValue;
}, 10);
2
3
由上图可以看出:提供
initialValue
10,那么accumulator
取initialValue
的值10,currentValue
取数组中的第一个值,currentIndex
为0。
特别说明:Array.prototype.reduceRight()
函数:接受一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值。Array.prototype.reduce()
是从左到右。
combineReducers.js
combineReducers.js
对应着redux
里的combineReducers
方法,主要作用就是合并多个reducer
。
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
// 逻辑很简单,仅仅做了一下错误信息的拼接
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionDescription =
(actionType && `action "${String(actionType)}"`) || 'an action'
return (
`Given ${actionDescription}, reducer "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
}
// 异常检测,找出state里面没有对应reducer的key,并提示开发者做调整
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
const reducerKeys = Object.keys(reducers) // 获取所有的有效Reducer对应的key
const argumentName =
action && action.type === ActionTypes.INIT
? 'preloadedState argument passed to createStore'
: 'previous state received by the reducer'
if (reducerKeys.length === 0) { // 判断reducers是否为{}空对象
return (
'Store does not have a valid reducer. Make sure the argument passed ' +
'to combineReducers is an object whose values are reducers.'
)
}
if (!isPlainObject(inputState)) { // 判断inputState是否为简单对象
return (
`The ${argumentName} has unexpected type of "` +
{}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
}
// 筛选出inputState里有的key,但reducers里没有的key
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
// 如果是替换reducer的action,则跳过,不打印异常信息
if (action && action.type === ActionTypes.REPLACE) return
// 将所有异常的key打印出来
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
// 检测finalReducers里的每个reducer是否都有默认返回值
function assertReducerShape(reducers) { // 接收所有有效的reducer
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
// 判断finalReducer中的reducer接受一个初始化action,是否依旧能够返回有效的state
const initialState = reducer(undefined, { type: ActionTypes.INIT })
if (typeof initialState === 'undefined') { // 返回无效的state,则抛出错误
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`
)
}
// 判断finalReducer中的reducer接受一个未知的action,是否依旧能够返回有效的state
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined'
) {
throw new Error( // 返回无效的state,则抛出错误
`Reducer "${key}" returned undefined when probed with a random type. ` +
`Don't try to handle ${
ActionTypes.INIT
} or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined, but can be null.`
)
}
})
}
// reducers Object类型 每个属性对应的值都要是function
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers) // 获取传入的reducers对象的所有key
const finalReducers = {} // 存储真正有效的reducer
// 从传入的reducers中筛选出有效的reducer
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存入finalReducers对应的key中
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers) // 获取有效的reducers对象的所有key
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {} // 开发模式下
}
let shapeAssertionError
// 这里assertReducerShape函数做的事情是:
// 检查finalReducer中的reducer接受一个初始action或一个未知的action时,是否依旧能够返回有效的值。
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// 返回一个函数,用于代理所有的reducer
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)
}
}
let hasChanged = false // 标识state是否发生变化
const nextState = {} // 存储新的state
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key] // 依次获取所有的子reducer
const previousStateForKey = state[key] // 获取到子reducer对应的旧state
const nextStateForKey = reducer(previousStateForKey, action) // 调用reducer得到新状态
// 对新的state是否为undefined的作校验
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey // 将得到的新状态存入nextState中
// 只要有一个子reducer对应的state发生了变化,hasChanged就为true
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state // state发生变化,返回nextState,否则返回state
}
}
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
assertReducerShape
方法主要检测两点:
- 不能占用
<redux/*>
的命名空间; - 如果遇到未知的
action
的类型,不需要用默认返回值。
- 如果传入
type
为@@redux/INIT<随机值>
的action
,返回undefined
,说明没有对初始化的action
的类型做响应,需要给对应的reducer
加默认值。 - 如果对应
type
为@@redux/PROBE_UNKNOWN_ACTION_<随机值>
返回为undefined
,说明占用了<redux/*>
命名空间。整个逻辑相对简单,好好自己梳理一下。
bindActionCreators.js
版本为:v4.0.1
function bindActionCreator(actionCreator, dispatch) {
return function() {
// actionCreators必须返回一个action
return dispatch(actionCreator.apply(this, arguments))
}
}
// bindActionCreators一般需要结合react-redux一起使用
export default function bindActionCreators(actionCreators, dispatch) {
// 当actionCreators是函数时
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// 判断actionCreators是否为null或者非对象
// 提示开发者actionCreators类型错误,应该是一个非空对象或者是函数。
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${
actionCreators === null ? 'null' : typeof actionCreators
}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
// actionCreators是对象的情况
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
// boundActionCreators中的每一个key都对应dispatch(actionCreator.apply(this, arguments))
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
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
Redux典型应用
const initialState = {
cash: 200 // 小金库初始金额
}
const reducer = (state = initialState, action) => {
const {type, payload} = action;
switch (type) {
case 'INCREMENT':
return Object.assign({}, state, {
cash: state.cash + payload
});
case 'DECREMENT':
return Object.assign({}, state, {
cash: state.cash - payload
});
default:
return state;
}
}
const reducers = Redux.combineReducers({treasury: reducer});
// 创建小金库
const store = Redux.createStore(reducers);
console.log(store.getState());
// 当小金库的现金发生变化时,打印当前的金额
store.subscribe(() => {
console.log(store.getState());
console.log(`余额:${store.getState().treasury.cash}`);
});
// 小明爸爸发了工资300块上交
store.dispatch({
type: 'INCREMENT',
payload: 300
});
// 小明拿着水电费单交100块水电费
store.dispatch({
type: 'DECREMENT',
payload: 100
});
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