Redux源码解读 - createStore篇

Redux源码解读 - createStore

Redux

什么是Redux

ReduxJavaScript状态容器,提供可预测化的状态管理。

相关的概念可以看Redux的中文网 Redux 中文文档

我自己的理解是,Redux规范了对数据的更改流程,使得数据可跟踪。

Redux,有三个概念性的东西:

  • Store:储存数据的容器;
  • Action:请求改变数据的动作;
  • Reducer:对请求的动作进行处理。

Redux中,如果你想要改变数据,你不能直接对状态进行操作,而是要通过动作,也就是Action来表明你的意图。

这里从网上看到一张图方便理解:

ok,先来使用Redux,来看看他到底是何方圣神

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
const { createStore } = require("redux");

// Action 动作
const ACTION = {
INCREMENT: "INCREMENT",
DECREMENT: "DECREMENT",
};

// Reducer 定义如何处理动作
const reducer = (state, action) => {
switch (action.type) {
case ACTION.INCREMENT:
return {
...state,
val: state.val + 1,
};
case ACTION.DECREMENT:
return {
...state,
val: state.val - 1,
};
default:
return state;
}
};

// Store 存放数据的中心
const store = createStore(reducer, { val: 1 });

// 观察者订阅,数据发生变化的时候打印
store.subscribe(() => {
console.log(store.getState());
});

// 请求动作
store.dispatch({
type: ACTION.INCREMENT,
});

Reducer对不同的动作来生成新的状态。

createStore根据Reducer来生成一个数据中心,这个数据中心可以订阅数据变化,可以进行动作的分发。

思想感觉还挺好理解的,不过对于这样写的好处,我还是有点蒙的,可能内功不够吧…

不过也不打紧,分析源码学习学习,和这些关系应该不大。

createStore

这次就来看看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
export default function createStore(reducer, preloadedState, enhancer) {
// ... 省略了一些参数的验证。

let currentReducer = reducer;
let currentState = preloadedState;
let currentListeners = [];
let nextListeners = currentListeners;
let isDispatching = false;

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

function getState() {
return currentState;
}

function subscribe(listener) {
let isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
isSubscribed = false;
ensureCanMutateNextListeners();
const index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null;
};
}

function dispatch(action) {
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
return action;
}

function replaceReducer(nextReducer) {
currentReducer = nextReducer;
dispatch({ type: ActionTypes.REPLACE });
}

function observable() {
const outerSubscribe = subscribe;
return {
subscribe(observer) {
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,
};
}

PS:这里我省略了一些参数的验证的代码,我个人认为刚开始读可以忽略一些不是特别重要的东西,抓住重点读才能学到东西(应该…

直接看整个函数的返回,发现它返回了一个对象,这个对象的属性都是一些方法。

  • dispatch
  • subscribe
  • getState
  • replaceReducer
  • [$$observable]

函数的开头定义了一些变量。

1
2
3
4
5
let currentReducer = reducer; // 传进来的reducer
let currentState = preloadedState; // 传进来的初始状态
let currentListeners = []; // 观察者函数回调函数的集合
let nextListeners = currentListeners; // currentListeners的浅拷贝
let isDispatching = false; // 是否正在处理Action

那我们就一个一个看

getState

这个函数最为的简单,函数返回当前的状态对象。
完整的代码如下

1
2
3
4
5
6
7
8
9
10
11
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;
}

先通过一个标志的变量isDispatching来判断当前是否正在分发action

如果是的话,那么这时的state就是不确定的,所以不能被获取。

subscribe

这个函数主要传入一个函数,在状态对象发生改变的时候会触发传入函数的执行。

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

函数先对参数listener进行验证,必须是一个函数才能执行下面的逻辑。

然后判断是否处于dispatch的执行过程,如果是也不行,因为会造成二义性。这个加入的回调函数到底要不要执行。

定义了一个变量isSubscribed表示是否订阅。

然后接下来执行ensureCanMutateNextListeners函数。

1
2
3
4
5
6
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
// 浅拷贝一份
nextListeners = currentListeners.slice();
}
}

为什么要通过nextListenerscurrentListeners来维护订阅的列表呢

我们可以用以下代码来解释,这里我写了一个简单的发布订阅代码来作例子

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
const subscribes = []; //订阅的回调数组

// 订阅函数
const subscribe = (callback) => {
// 添加到数组中
subscribes.push(callback);
// 返回一个取消订阅的函数
return () => {
const index = subscribes.indexOf(callback);
subscribes.splice(index, 1);
};
};

// 通知函数
const notify = () => {
for (let i = 0; i < subscribes.length; i++) {
subscribes[i]();
}
};

let un1, un2;

un1 = subscribe(() => {
console.log("1");
un2 = subscribe(() => {
console.log("2");
});
});

notify();

这段函数有意思的点就在于在一个订阅的回调中又添加了一个订阅

这样写有啥问题呢,运行起来也是输出了 1 和 2,感觉上并没有什么不对的

但是如果notify函数写法稍微变一下那么问题就出现了

1
2
3
4
5
6
const len = subscribes.length;
const notify = () => {
for (let i = 0; i < len; i++) {
subscribes[i]();
}
};

先保存了回调数组的长度,这样回调中如果订阅了新的回调。

那么就不会触发这个新的回调。

这就会导致逻辑上的混乱。

Redux中,解决的办法就是把在回调中的订阅延迟到下一个状态改变时再触发。

subscribe的逻辑中,都是使用了nextListeners这个变量。

而在dispatch函数中使用的是currentListeners这个变量。

1
2
3
4
5
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}

在回调前把nextListeners赋给currentListeners

这样如果在回调中产生了新的订阅或者取消订阅的话。

就会执行ensureCanMutateNextListeners函数。

而这个函数很简单,如果nextListenerscurrentListeners相等,就把浅拷贝一份currentListeners赋给nextListeners

这样就避免了逻辑的混乱。

subscribe返回了一个函数,这个函数的作用就取消订阅。

和订阅一样,如果在回调中取消了订阅,都会延迟到下一次状态变化再执行。

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
27
28
29
30
31
32
33
34
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 = currentReducer(currentState, action);
} finally {
isDispatching = false;
}

const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}

return action;
}

第一个判断action必须是一个字面对象。

可以看下isPlainObject的逻辑。

1
2
3
4
5
6
7
8
9
10
export default function isPlainObject(obj) {
if (typeof obj !== "object" || obj === null) return false;

let proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}

return Object.getPrototypeOf(obj) === proto;
}

先用简单的typeof操作符来判断,顺便把null的情况也给排除出去。

接下来就是要排除那些自定义的构造函数所产生的对象。

Object.getPrototypeOf获取一个对象的原型。

通过while循环不断遍历原型链,直到最顶端的原型。

排除的依据就是如果原型链上出现了除了Object.prototype之外的原型,就返回false

1
2
3
4
5
6
if (typeof action.type === "undefined") {
throw new Error(
'Actions may not have an undefined "type" property. ' +
"Have you misspelled a constant?"
);
}

接下来判断了actiontype属性,他不能是undefined

1
2
3
if (isDispatching) {
throw new Error("Reducers may not dispatch actions.");
}

然后如果前一个dispatch还在执行,那么本次的dispatch就会失败。保证不会出现竟态。

接下来就是更新state的过程。

1
2
3
4
5
6
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}

isDispatching这个标志变量保证执行reducer时其他操作的限制。

最后就是触发订阅的回调函数,返回action对象了。

1
2
3
4
5
6
7
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}

return action;

replaceReducer

1
2
3
4
5
6
7
8
9
function replaceReducer(nextReducer) {
if (typeof nextReducer !== "function") {
throw new Error("Expected the nextReducer to be a function.");
}

currentReducer = nextReducer;

dispatch({ type: ActionTypes.REPLACE });
}

这个函数作用就是替换新的reducer处理函数。

[$$observable]

[$$observable]也就是内部的observable函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
},
};
}

这个函数作用就是适配观察者的其他实现。

返回的对象中subscribe方法可以传入一个观察者。

在内部中构造了一个observeState方法,并且store订阅了这个方法。

而这个方法的内容就是调用传入的observer对象调用他的next方法,仅此而已。

简单点讲就是传递变化后的数据。

比如使用Rxjs的话,可以这么写:

1
2
3
4
5
6
7
8
9
const store = //...

const observable = Observable.create(observer => {
const unsubscribe = store.observable().subscribe(observer);
});

observable.subscribe(state => {
console.log(state); // 每次状态改变,这里就会执行
});

ok,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
// 防止传入多个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的情况
if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
enhancer = preloadedState;
preloadedState = undefined;
}

// enhancer必须是一个函数
if (typeof enhancer !== "undefined") {
if (typeof enhancer !== "function") {
throw new Error("Expected the enhancer to be a function.");
}

return enhancer(createStore)(reducer, preloadedState);
}

// reducer必须是一个函数
if (typeof reducer !== "function") {
throw new Error("Expected the reducer to be a function.");
}

以及在返回值的上方,会在内部先执行一次dispatch来设置默认的state

1
dispatch({ type: ActionTypes.INIT });

如果没在第二个参数指定默认值的话,可以在reducer函数里面设置默认值。

1
2
3
4
5
6
7
8
const reducer = (state = defaultState, action) => {
switch (action.type) {
case ...
case ...
default:
return state;
}
};

通过createStore传入的初始state优先级是比在reducer中要高的。

因为在函数的开头定义的内部变量中,currentState会保存preloadedState的值。

1
let currentState = preloadedState;

而通过函数参数的默认值只有在currentStateundefined时才会触发。

文章参考了网上的一些文章,在此非常感谢。