redux源码 | Part1_ createStore

学习资料

redux源码 v 4.x

学习目标

  1. redux源码createStore(Part1)
  2. redux中间件原理(Part2)
  3. redux源码combineReducers(Part3)

Redux介绍

Redux是什么

  • redux是专门用于做状态管理的JS库
  • redux的作用是集中式管理react应用中多个组件共享的状态

什么时候需要用redux

  • 某个组件的状态,需要让其他组件可以随时拿到(共享)
  • 一个组件需要改变另一个组件的状态(通信)

Redux组成

State 状态

状态即传递的数据

在React开发的项目的时候,大致可以把State分为三类:

  • DomainDate: 可以理解为成为服务器端的数据,比如:获取用户的信息,商品的列表等等
  • UI State: 决定当前UI决定展现的状态,比如:弹框的显示隐藏,受控组件等等
  • App State: App级别的状态,比如:当前是否请求loading,当前路由信息等可能被多个和组件去使用的到的状态

Action 事件

Action是把数据从应用传到store的载体,是数据的唯一来源,一般来说,可以通过store.dispatch()将action传递给store

  • action包含2个属性
    • type:标识属性, 值为字符串, 唯一, 必要属性
    • data:数据属性, 值类型任意, 可选属性
  • 例子:{ type: ‘ADD_STUDENT’,data:{name: ‘tom’,age:18} }

Reducer

  • Reducer的本质就是一个函数,它用来响应发送过来的actions,然后经过处理,把state发送给Store的,用于初始化状态加工状态
  • 在Reducer函数中,需要return返回值,这样Store才能接收到数据,函数会接收两个参数,一个参数是初始化state【旧】,第二个参数是action,加工时,根据旧的state和action, 产生新的state的纯函数
1
2
3
const initState = {...};

rootReducer = (state = initState, action) => {...return {...}};

Redux三大核心概念

单一数据源

整个应用的state被存储在一颗object tree中,并且这个object tree只存在唯一一个store中,使得状态之间更好管理,更容易调试,以及一些撤销/重做的功能更容易

state是只读的

唯一改变state的方法是通过dispatch触发action

这样能够确保视图和网络请求都不能直接去修改state,相反,它们只能表达想要修改的意图,因为所有的修改都被集中化处理,并且严格按照一个接一个的顺序执行

使用纯函数来执行修改

纯函数

纯函数

  • 数据不可变(Immutable Data):此函数在相同的输入值时,需产生相同的输出
  • 无状态(Statelessness):函数的输出和输入值以外的其他隐藏信息或状态无关,也和由IO设备产生的外部输出无关
  • 没有副作用(No Side Effects):该函数不能有语义上可观察的函数副作用,例如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等

为了描述action如何改变state tree,你需要编写reducers

Reducers只是一些纯函数,它接收先前的state和action,并且返回新的state, 可以复用,可以控制顺序,传入附加参数State状态

Redux工作机制

过程解析

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
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
import $$observable from 'symbol-observable'

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

/*
此函数用于创建Redux store(store可以认为是一个管理state的仓库)
值得注意的是:
- dispatch是改变state的唯一方法【核心概念2:state只读】
- app里面只能有一个store【核心概念1:单一数据源】:
如果你的app有很多模块的话,你可以通过多个reducer来管理,通过combineReducer来组合reducer

参数说明:
1. reducer {Function} 通过前面的说明已经知道reducer是一个纯函数,用于初始化状态和加工状态
2. preloadedState {any} 初始状态
3. enhancer {Function} 增强器(可认为是拓展功能),比如中间件等等 Redux 附带的唯一存储增强器是`applyMiddleware()`。
返回值说明:
store
有如下功能:
- read state 读取状态 - getSate
- dispatch action 调度动作 - dispatch
- subscribe to changes 监听变化 - subscribe

[replaceReducer和observable不是很重要]

*/
export default function createStore(reducer, preloadedState, enhancer) {
/*
Step1:参数校验
1. 校验1:直接传入多个enhancers的情况,下面注释说明了,你可能是传入了多个enhancers,直接传入是不对的,你需要把它们组合(compose them)
2. 检验2:没传入初始状态的情况,也就是你只穿了两个参数,那么你的第二个参数应该是enhancer,而preloadedState为undefined
3. 校验3:检验enhancer是不是一个函数
4. 校验4:reducer是不是一个函数
*/
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.'
)
}

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.')
}

return enhancer(createStore)(reducer, preloadedState)
}

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

/*
Step2:定义内部变量
1. 变量1:currentReducer = reducer | 当前的reducer
2. 变量2:currentState = preloadedState | 当前的state:可被getState()获取
3. 变量3:currentListeners = [] | 监听列表
4. 变量4:nextListeners = currentListeners | 监听列表
5. 变量5:isDispatching = false | 是否正在被dispatch(调度)
*/
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false

/*
ensureCanMutateNextListeners函数:
生成currentListeners的浅拷贝,可以把nextListeners作为临时调度列表
这可以防止消费者调用的任何错误在调度过程中订阅/取消订阅

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

/*
getState函数
返回当前状态(currentState)
*/
function getState() {
/*
获取state的时候必须保证这个reducer已经结束工作了,store已经接收到新的state了,才能调用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函数:作用是订阅监听
任何时候dispatch一个action都会调用这个函数【一触即发!】
调用action后state object tree的部分可能已经改变了
所以可以再去调用getState()读取回调内的当前状态树

参数说明:
- listener:监听到变化后执行的函数,比如subscribe(function() {console.log(1)}) 变化后打印1
返回值说明:
- unsubscribe {Fuction}: 返回一个具有取消订阅功能的函数

*/
function subscribe(listener) {
/*
listener必须是一个函数
举个例子,listener是一个render函数,调用subscribe就会重新渲染某部分的页面(取决于你的render函数需要render什么)
*/
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}

/*
同理:获取state的时候必须保证这个reducer已经结束工作了,store已经接收到新的state了,才能调用getState
这也就是说明,顺序是 dispatch action -> state改变 -> 被subscribe(listener) 监听到 -> 获取新的状态 -> listener函数调用
举例: 计时器:dispatch action [目的在于使得value+1] -> value状态改变 -> 被subscribe(render) 监听到 -> 获取新的state -> 重新渲染
*/
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.'
)
}

// Sub-Step1: 设置isSubscribed 为 true
let isSubscribed = true

// Sub-Step2: (1) 浅复制currentListeners[当前的监听列表],(2) 把监听函数push到监听列表中
ensureCanMutateNextListeners()
nextListeners.push(listener)

/*
unsubscribe函数:具有取消订阅功能
*/
return function unsubscribe() {
// 只有isSubscribed为true的才能被取消订阅
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.'
)
}

// sub-step3: 修改isSubscribed 为 false
isSubscribed = false

// sub-step4: 浅复制监听列表
ensureCanMutateNextListeners()

// sub-step5: 监听完毕,从列表中删除它
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}

/*
dispatch函数:[调度函数] 划重点!回顾:这是改变状态的唯一方法!!!!! 【核心概念2:state只读】

dispatch被调用后,reducer将会被调用,store通过转发dispatch(action) 中包含的previousState和action给redeucer,reducer返回newState给store,React component可以通过getState获取newState
注意:基本实现仅仅支持普通对象的操作,如果的状态是一个Promise Obserable thunk等等,你需要借助与中间件(middleware) 【后面介绍】
参数说明:
- action 是一个object,格式为 {type:xxx, data:xxx}
返回值说明:
- {Object} 为了方便,返回action对象,但是里面的data是经过更改了
*/

function dispatch(action) {
/*
isPlainObject函数不贴代码了~看源码的意思,isPlainObject就是用来判断action是不是一个对象
*/
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}

/*
只有定义了action.type才能进行后续操作,所以type不能是undefined
*/
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}

/*
正在被dispatch的action不能传入到reducer中处理
*/
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}

/*
sub-step1:
- 切换isDispatching为true
- currentState为currentReducer(currentState, action)处理后返回的state
reducer函数举例:
function currentReducer(state={count:0}, action) => {
if (action.type === 'INCREMENT') {
return {...state, count: state.count+1}
}
}
执行完毕后,也就是currentState返回后,
切换isDispatching为false
*/
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}

/*
sub-step2:
触发listener函数
*/
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}

return action
}


/*
replaceReducer函数:替换store当前使用的reducer来计算 state
不是特别重要,就是替换当前的reducer

*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}

currentReducer = nextReducer

dispatch({ type: ActionTypes.REPLACE })
}

/*
observable函数:
不重要~暂时不说了
*/
function observable() {
const outerSubscribe = subscribe
return {

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
}
}
}

dispatch({ type: ActionTypes.INIT })

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

整个createStore函数可以抽象为下图:

接下来就稍微解释下getState dispatch subscribe是如何工作的(省略参数校验部分)

首先,在createStore函数内部维护了以下几个变量,并且初始化:

1
2
3
4
5
变量1:currentReducer = reducer             | 当前的reducer 
变量2:currentState = preloadedState | 当前的state:可被getState()获取
变量3:currentListeners = [] | 监听列表
变量4:nextListeners = currentListeners | 监听列表
变量5:isDispatching = false | 是否正在被dispatch(调度)

getState

函数思路非常的简单,就是获取currentState,也就是当前的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getState() {
/*
获取state的时候必须保证这个reducer已经结束工作了,store已经接收到新的state了,才能调用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

dispatch函数是用于调度action的,思路如下:

  • 首先需要确保action的isDispatching为false
  • 切换isDispatching的状态为true,说明这个action要被dispatch了
  • 把action和currentState传入reducer中,并且产出newState【但仍然以currentState命名】
  • 切换isDispatching的状态为false,说明这个action要dispatch结束了
  • 因为state发生了变化,所以触发listener函数,可能有很多listener函数,所以维护了一个列表,需要去遍历这个列表
  • 最终返回action,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
function dispatch(action) {	
/*
正在被dispatch的action不能传入到reducer中处理
*/
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}

/*
sub-step1:
- 切换isDispatching为true
- currentState为currentReducer(currentState, action)处理后返回的state
reducer函数举例:
function currentReducer(state={count:0}, action) => {
if (action.type === 'INCREMENT') {
return {...state, count: state.count+1}
}
}
执行完毕后,也就是currentState返回后,
切换isDispatching为false
*/
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}

/*
sub-step2:
触发listener函数
*/
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}

return action
}

subscribe

subscribe函数有订阅监听的意思,就是把监听函数放入到列表里面,然后在dispatch的时候去遍历列表

  • 首先要保证listener是一个函数,结合dispatch函数,state发生变化的时候,需要执行listener,并且要保证没有在调度中
  • 设置isSubscribed 为 true,表示正在被订阅
  • 把listener加入到nextListeners列表
  • 返回一个取消订阅的函数
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
function subscribe(listener) {
/*
listener必须是一个函数
举个例子,listener是一个render函数,调用subscribe就会重新渲染某部分的页面(取决于你的render函数需要render什么)
*/
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.'
)
}

// Sub-Step1: 设置isSubscribed 为 true
let isSubscribed = true

// Sub-Step2: (1) 浅复制currentListeners[当前的监听列表],(2) 把监听函数push到监听列表中
ensureCanMutateNextListeners()
nextListeners.push(listener)

/*
unsubscribe函数:具有取消订阅功能
*/
return function unsubscribe() {
// 只有isSubscribed为true的才能被取消订阅
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.'
)
}

// sub-step3: 修改isSubscribed 为 false
isSubscribed = false

// sub-step4: 浅复制监听列表
ensureCanMutateNextListeners()

// sub-step5: 监听完毕,从列表中删除它
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}

实例example_计时器

html部分

1
2
3
4
5
6
7
8
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
<div class="box">
<p>
Click<span id="value">0</span>
<button id="increment">+</button>
<button id="decrement">-</button>
</p>
</div>

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
// 获取按钮和展示value的标签
const spanValue = document.getElementById('value');
const incrementBtn = document.getElementById('increment');
const decrementBtn = document.getElementById('decrement');
// 封装一个reducer函数:纯函数,用于初始化状态和加工状态
/*
传入两个参数state和action
{
type: xxx,
data: xxx,
}

*/
function count(state, action) {
if (typeof state === 'undefined') return 0;

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

// 创建一个store,参数参数为reducer = count,其余两个参数忽略
const store = Redux.createStore(count);
console.log(store);

// 设置listener函数动态展示数据
const listener = function() {
spanValue.innerText = store.getState().toString();
}

// 订阅listener
store.subscribe(listener);

// 设置dispatch
incrementBtn.addEventListener("click", () => {
store.dispatch({type:'INCREMENT'})
})

decrementBtn.addEventListener("click", () => {
store.dispatch({type:'DECREMENT'})
})