Redux源码系列一--createStore

本文根据Redux 4.0.1版本来分析 createStore 源代码

标签:#Redux, #Source code analysis

使用方法

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

import thunkMiddleware from "redux-thunk";

import { reducer as weatherReducer } from "./weather/";

const reducer = combineReducers({
weather: weatherReducer
});

const middlewares = [thunkMiddleware];

const storeEnhancers = compose(applyMiddleware(...middlewares));

export default createStore(reducer, {}, storeEnhancers);

源码解读

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
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
import $$observable from 'symbol-observable'

import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'

/**
* 创建一个Redux store用来存储所有的state
* Creates a Redux store that holds the state tree.
*
* State只读原则: 不能够直接改变state值,只能够通过 `dispatch`方法来更改store状态树
* The only way to change the data in the store is to call `dispatch()` on it.
*
* 数据来源单一原则:整个应用中只有一个store,可以通过`combineReducers`方法来将多个reducers
* 结合成一个单一的reducer
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
*
* reducer(必填参数):通过previousState和当前action返回一个新的状态数据树
* @param {Function} reducer A function that returns the next state tree, given
* the current state tree and the action to handle.
*
* 容器初始状态(可选参数):
* 初始时的 state。 在同构应用中,你可以决定是否把服务端传来的 state 水合(hydrate)后传给它,
* 或者从之前保存的用户会话中恢复一个传给它。如果你使用 combineReducers 创建 reducer,
* 它必须是一个普通对象,与传入的 keys 保持同样的结构。否则,你可以自由传入任何 reducer 可理解的内容。
* @param {any} [preloadedState] The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
*
* enhancer增强器(可选参数):Store enhancer 是一个组合 store creator 的高阶函数,
* 返回一个新的强化过的 store creator。这与 middleware 相似,它也允许你通过复合函数改变 store 接口。
* @param {Function} [enhancer] The store enhancer. You may optionally specify it
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
*
* store: 保存了应用所有 state 的对象。改变 state 的惟一方法是 dispatch action。
* 你也可以 subscribe 监听 state 的变化,然后更新 UI。
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
*/
export default function createStore(reducer, preloadedState, enhancer) {
// 不支持传入多个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.'
)
}

// 如果第二个参数传入一个函数,则将第二个参数作为enhancer,初始状态置为undefined
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.')
}

// 如果传入enhancer合法,则通过enhancer再次调用createStore
return enhancer(createStore)(reducer, preloadedState)
}

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

let currentReducer = reducer
let currentState = preloadedState
let currentListeners = [] // 用来存储所有的监听函数
let nextListeners = currentListeners
let isDispatching = false // 是否正在派发事件

/**
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
*/
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}

/**
* Reads the state tree managed by the store.
* 用来返回应用当前的页面数据状态树
* @returns {any} The current state tree of your application.
*/
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
}

/**
* 订阅store变化: 注册每一次数据更新后的回调逻辑,这种回调逻辑往往是页面渲染,在回调逻辑中,使用store.getState()
* 获取最新数据
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
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#subscribe(listener) for more details.'
)
}

let isSubscribed = true

ensureCanMutateNextListeners() //防篡改
// subscribe可能会被调用多次,每一次调用时,都将相关的监听函数存入listeners数组中
nextListeners.push(listener)

// subscribe函数被调用后返回一个取消订阅的函数
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#subscribe(listener) for more details.'
)
}

isSubscribed = false

ensureCanMutateNextListeners()
// 取消订阅
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}

/**
* 派发一个action,在Redux中,dispatch是唯一的改变state的方法
* Dispatches an action. It is the only way to trigger a state change.
*
* reducer函数,在Redux数据流里,reducer在具备初始状态的情况下,每一次运算其实都是根据
* 之前的状态(previous state)和现有的action来更新state的,这个state可以理解为上下文中
* 累加器的结果,作用类似于数组中的reduce方法
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* action必须是原生对象,如果想要派发一个Promise、Observable、thunk函数,或者其他任何形式,
* 需要将store创建函数包裹进相应的中间件,例如:redux-thunk
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* action 是一个对象,必须有一个type属性,且不能为`undefined`,描述了一种变化,并携带这种变化的数据信息
* @param {Object} action A plain object representing “what changed”. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
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?'
)
}

// 调用dispatch的时候只能一个一个调用,通过isDispatching调用
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}

try {
isDispatching = true
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
}

/**
* Replaces the reducer currently used by the store to calculate the state.
*
* 替换reducer,在代码分割、reducer动态加载、redux热部署
* 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}
*/
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 })
}

/**
* 为了兼容一些observable/reactive的库
* 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
*/
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.
/**
* 当 store 创建后,Redux 会 dispatch 一个 action 到 reducer 上,
* 来用初始的 state 来填充 store。你不需要处理这个 action。
*/
dispatch({ type: ActionTypes.INIT })

return {
dispatch, // 派发Action
subscribe, // 订阅页面数据状态,即store中state的变化
getState, // 获取当前页面状态数据树,即store中state的变化
replaceReducer, // 一般开发用不到,热更新/代码分离使用
[$$observable]: observable
}
}

参考文献:

1、Redux 官方文档

2、Redux 入坑进阶-源码解析

3、《React 状态管理与同构实战》

友情提示:请尊重作者劳动成果,如需转载本博客文章请注明出处!谢谢合作!

【作者:吴林  https://super-lin0.github.io/