Redux源码浅读

本文是关于 redux(3.7.2)源代码的一些浅读

redux源码目录中 ,可以看到以下文件目录:

1
2
3
4
5
6
7
8
|-- utils/
|-- warning.js //打印error
|-- 1. applyMiddleware.js //
|-- 2. bindActionCreators.js //
|-- 3. combineReducers.js //
|-- 4. compose.js //
|-- 5. createStore.js //
|-- index.js //入口文件

与文件对应的,主要也是介绍 createStore compose combineReducers bindActionCreators applyMiddleware这几个函数。

1. compose

先来看看compose函数,这个比较简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function compose() {
//funcs保存着所有参数函数的数组
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}

//省略一些逻辑判断……

//reduce方法使用指定的函数将数组元素进行组合,生成单个值
return funcs.reduce(function (a, b) {
return function () {
return a(b.apply(undefined, arguments));
};
});
}

其中主要就是对 reduce 的理解了。reduce()方法使用指定的函数将数组元素进行组合(从左到右),生成单个值。具体可以查看Array.prototype.reduce() | MDN

compose的功能为从右到左,组合参数(函数)。传递给compose方法的参数是函数类型的,

compose(f, g, h) 相当于 (…args) => f(g(h(…args)))

2. applyMiddleware 与 中间件链

故名思义这个函数就是应用中间件,一般使用方式为:

1
2
//thunk代表react-thunk中间件
const store = createStore(rootReducer, initialState, applyMiddleware(thunk));

2.1 中间件及react-thunk

在看applyMiddleware函数前,先来简单看一下中间件,redux中间件在发起action到达reducer之间扩展了一些功能。一般用于记录日志(reudx-logger)和增加异步调用(redux-thunk , redux-saga)等。这些中间件一般按照一定的格式书写,比如react-thunk2.2.0的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//react-thunk2.2.0的代码,很简单 只有十几行……
//外层函数可以传入多余的参数
function createThunkMiddleware(extraArgument) {
//获取dispatch getState(是由applyMiddleware传入)
return function (_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
//1. 如果这个中间件是调用链最内环的,next指原store.dispatch
//2. 其他next一般指上一个中间件的返回值 action => {}
//对于这个next的赋值不清楚的话可以结合之后的applyMiddleware函数
return function (next) {
return function (action) {
//1. 原action只是个普通的对象,thunk使action可以传入函数类型,并传入了dispatch, getState, extraArgument
//2. 如果action是个异步函数,thunk会调用该函数
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
//3. 函数调用结束后,获取必要的数据再次触发dispatch由此实现异步效果。
return next(action);
};
};
};
}

可以看到react-thunk中间件是一个多层的高阶函数,格式大致为:

1
({dispatch,getState}) => next => action => {return next(action)}

中间件的格式都要遵循这样相似的格式,这是由applyMiddleware函数决定的。接下来看下applyMiddleware的源码:

2.2 applyMiddleware源码

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
//applyMiddleware源码
export default function applyMiddleware() {
//middlewares保存传进来的中间件
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}

//createStore是创建createStore的函数,会在下文解读,这里先不管
return function (createStore) {
return function (reducer, preloadedState, enhancer) {
//创建store 并获取了其dispatch方法
var store = createStore(reducer, preloadedState, enhancer);
var _dispatch = store.dispatch;
var chain = [];

//用于传递给中间件第一层函数的参数,上文在thunk中有看到
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch(action) {
return _dispatch(action);
}
};
//middlewares保存的是中间件,chain对应保存的就是中间件第二层函数组成的数组
//形象点就是上文中间件格式去掉第一层:next => action => {}
chain = middlewares.map(function (middleware) {
return middleware(middlewareAPI);
});
//1. componse的功能在上文说到,假设chain为[F1,F2,F3],compose之后变成了F1(F2(F3))
//2. 与上文thunk中说到中间件格式对应,F3是中间件链的最内环 所以F3的next参数为store.dispatch
//3. F2的next参数就是F3返回的 action => {}
//4. 同样的F1的next参数就是F2返回的 action => {}
//
//_dispatch就相当于F1(F2(F3(store.dispatch)))
//这样多个中间件就组合到了一起,形成了中间件链
_dispatch = compose.apply(undefined, chain)(store.dispatch);

//新的dispatch会覆盖原dispatch,之后调用dispatch同时会调用中间件链
return _extends({}, store, {
dispatch: _dispatch
});
};
};
}

_dispatch = compose.apply(undefined, chain)(store.dispatch);组合后,中间件的next就是store.dispatch一路经过上一个中间件封装后的变种dispatch。

2.3 中间件链的执行顺序

光看代码可能对于中间件链的执行顺序的理解还是优点蒙,这里来做个实践加深一下理解。

一、 先按上文说的中间件格式({dispatch,getState}) => next => action => {return next(action)} 写三个中间件:

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

function middleware1({dispatch,getState}) {
return function(next) {
console.log('middleware1 next层 next:',next);
return function(action) {
console.log('middleware1 action层 开始')
next(action)
console.log('middleware1 action层 结束')
}
}
}
function middleware2({dispatch,getState}) {
return function(next) {
console.log('middleware2 next层 next:',next);
return function(action) {
console.log('middleware2 action层 开始')
next(action)
console.log('middleware2 action层 结束')
}
}
}
function middleware3({dispatch,getState}) {
return function(next) {
console.log('middleware3 next层 next:',next);
return function(action) {
console.log('middleware3 action层 开始')
next(action)
console.log('middleware3 action层 结束')
}
}
}

二、 将它们和redux-thunk 和 redux-logger一起加入中间件链:

1
2
3
4
5
6
7
8
9
10
11
const middlewares = [
middleware1,
thunkMiddleWare,
middleware2,
middleware3,
loggerMiddleWare,//redux-logger需要放在最后面
];

...

const store = createStore(rootReducer, initialState, applyMiddleware(...middlewares));

运行你的代码后在chrome控制台看到如下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
middleware3 next层 next: ƒ (l){if("function"==typeof

middleware2 next层 next: ƒ (action){
console.log('middleware3 action层 开始');
next(action);
console.log('middleware3 action层 结束');
}

middleware1 next层 next: ƒ (action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}

return next(action);
}

这也验证了上文对 _dispatch = compose.apply(undefined, chain)(store.dispatch);的理解。

这里的中间件链是由 [m1,thunk,m2,m3,logger]组成,在compose之后变成了 m1(thunk(m2(m3(logger)))),
所以

  • m3的next参数是logger的action层函数(参数为action那层函数)
  • m2的next参数是m3的action层函数
  • m1的next参数是thunk的action层函数

三、 执行一下 dispatch(action) :

这时候分两种情况:

1、当action类型为对象时,在chrome控制台看到如下信息:

1
2
3
4
5
6
7
8
9
发起 dispatch(action) action类型为对象

middleware1 action层 开始
middleware2 action层 开始
middleware3 action层 开始
action SOME_OBJ_ACTION redux-logger.js:1
middleware3 action层 结束
middleware2 action层 结束
middleware1 action层 结束

粗粗一看好像顺序不对啊,不该先执行middleware3的逻辑嘛?其实内层(位置靠后的中间件)只是返回了一个function,并没有执行其中的逻辑,不断由外层的中间件包裹形成了一个‘洋葱模型’。由外向内穿心而过,再由内向外完成流程。

这样子就很明确了,中间件的action层执行顺序为先加入中间件链的先执行!,更准确的说中间件中先执行从外层向内层中 next(action)之前的逻辑,然后执行从内层向外层中 next(action)之后的逻辑。

2、当action类型为函数时,在chrome控制台看到如下信息:

1
2
3
4
发起  dispatch(action) action类型为函数

middleware1 action层 开始
middleware1 action层 结束

可以看到只执行了 middleware1 和 thunk 两个中间件!这是因为thunk没有执行 next(action) 中断了中间件链! 当中间件没有执行next(action)时会导致中间件链中断,这是因为dispatch没有传递下去,所以中间件还可以捣乱咯~

3. bindActionCreators

bindActionCreators的功能是为action creaters包装上dispatch,使其调用action时自动dispatch对应的action。

在bindActionCreators.js中只有两个函数 bindActionCreatorbindActionCreators

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
//这个函数的主要作用就是返回一个函数,当我们调用返回的这个函数的时候,就会自动的dispatch对应的action
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(undefined, arguments));
};
}

export default function bindActionCreators(actionCreators, dispatch) {

//省略一些逻辑判断

// 获取所有action creater函数的名字
var keys = Object.keys(actionCreators);
// 保存dispatch和action creater函数进行绑定之后的集合
var boundActionCreators = {};
//为每个actionCreators 包装上 dispatch
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var actionCreator = actionCreators[key];
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreators;
}

4. combineReducers

Reducer只是一些纯函数,它接收之前的state和action,并返回新的state。当应用变大,我们可以拆分多个小的reducers,分别独立的操作state tree的不同部分。而combineReducers就是将多个不同的reducer合并成一个最终的reducer,用于赋值给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
//combineReducers的代码比较简单,在省略一些错误判断的代码后:
export default function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];

......

if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
var finalReducerKeys = Object.keys(finalReducers);

//从上面到这里都是为了保存finalReducerKeys 和 finalReducers
......

//返回的combination函数就相当于结合所有reducers之后新的reducer
return function combination() {
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var action = arguments[1];

......

var hasChanged = false;
var nextState = {};
//这里遍历了所有之前自定义的reducers,并记录下是否state有改变,并记录下改变的state
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key];
var previousStateForKey = state[_key];
//遍历所有的reducer,若previousStateForKey匹配到则返回新的state
//若匹配不到就在reducer中dufault中返回原state
var nextStateForKey = reducer(previousStateForKey, action);
......
nextState[_key] = nextStateForKey;
//这里有点意思,并没有因为找到不同的state就直接返回
//这意味着,多个子reducers可以对同个action返回自己的state
//并且返回的state是依据靠后的reducer的返回值决定的
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
return hasChanged ? nextState : state;
};
}

5. createStore

现在来看下最重要的createStore函数:

1
2
3
4
5
6
/*
* redux有且仅有一个store,createStore函数就是用于创建一个store用来存放所有的state。
* @param {Function} reducer
* @param {any} [preloadedState] 初始化state
* @param {Function} [enhancer] store的增强器,有applyMiddleware、时间旅行(time travel)、持久化(persistence)等。
*/

上面是createStore的参数,其中enhancer指的是store的增强器(对store API进行改造)。

  • applyMiddleware在前面我们已经见过了,applyMiddleware是对store的dispatch函数的修改。
  • 时间旅行则是指redux可以回到任意以前的状态。
    • 这是因为Redux使用简单的对象来表示state状态,并使用纯函数计算下一个状态。这意味着如果给定一个特定state状态和一个特定action操作,那么下一个状态将始终完全相同。这种特性可以让我们将所有修改过的状态保存在一个状态历史对象中,通过指定恢复到状态历史对象中从前的状态,来完成时间旅行。
    • 比较容易可以想到的应用就是一些撤销/重做操作。
  • 持久化持久化这个概念肯定大家都熟悉,也有人做出了实现:redux-persist

介绍完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
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
export default function createStore(reducer, preloadedState, enhancer) {
var _ref2;

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

......

var currentReducer = reducer; //
var currentState = preloadedState; //当前的state
var currentListeners = []; //订阅函数
var nextListeners = currentListeners;//订阅函数备份,
//用于解决listeners数组执行过程(for循环)中,取消订阅listener产生的listeners数组index错误。
//这样保证在某个dispatch后,会保证在这个dispatch之前的所有事件监听器全部执行

var isDispatching = false; //dispatch方法同步标志

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

/**
* @returns {any} 当前state
*/
function getState() {
return currentState;
}

/*
* 将一个订阅函数放到 listeners 队列里,当 dispatch 的时候,逐一调用 listeners 中的回调方法。
* @param {Function} listener函数
* @return {Function} 解除绑定的方法
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.');
}

var isSubscribed = true;

ensureCanMutateNextListeners();
nextListeners.push(listener);

//解除订阅的方法,
return function unsubscribe() {
if (!isSubscribed) {
return;
}

isSubscribed = false;

ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
}

/**
* dispatch方法,调用reducer
*
* @param {Object} action
*
* @returns {Object} 一般会返回action,
* 如果使用了中间件,可能返回promise 或者function之类的()
*/
function dispatch(action) {
......

//dispatch是同步的,用isDispatching标志来判断
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
//调用reducer
try {
isDispatching = true;
//更新state树
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}

//调用nextListeners中的监听方法
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
var 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;
//触发生成新的state树
dispatch({ type: ActionTypes.INIT });
}

/**
*略
*/
function observable() {
......
}

// 生成初始state树
dispatch({ type: ActionTypes.INIT });

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

以上就是我个人的简单见解,如果有什么错误之处,敬请指导讨论


参考资料: