redux深入学习之中间件机制

上一篇文章讲解了redux如何使用,本篇文章将进一步深入,从redux的源码入手,深入学习redux的中间件机制。
在这里我们会以一个redux-thunk中间件为例,逐步分解redux的中间机制如何操作,如何执行。
闲话不多说,上代码。

如何加载中间件

1
2
3
4
5
6
7
8
9
10
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';

// create a store that has redux-thunk middleware enabled
const createStoreWithMiddleware = applyMiddleware(
thunk
)(createStore);

const store = createStoreWithMiddleware(rootReducer);

这里需要用到redux中提供的一个工具方法,叫做applyMiddleware,向该方法传入你想要使用的中间件,完了之后再传入createStore方法,
最终形成新的创建store的方法。

这显然是一个装饰器模式,通过不同的中间件对createStore方法进行修饰,最后形成新的createStore方法,那么创建的store就具有这些中间件的特性,
非常出色的设计,惊喜不仅在这,看了之后的代码你就更不得不佩服作者的代码设计能力。

瞬间觉得别人都是码神,而我就是码农有木有/(ㄒoㄒ)/~~

中间件加载机制的实现

先来看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
import compose from './compose';

/**
* Creates a store enhancer that applies middleware to the dispatch method
* of the Redux store. This is handy for a variety of tasks, such as expressing
* asynchronous actions in a concise manner, or logging every action payload.
*
* See `redux-thunk` package as an example of the Redux middleware.
*
* Because middleware is potentially asynchronous, this should be the first
* store enhancer in the composition chain.
*
* Note that each middleware will be given the `dispatch` and `getState` functions
* as named arguments.
*
* @param {...Function} middlewares The middleware chain to be applied.
* @returns {Function} A store enhancer applying the middleware.
*/
export default function applyMiddleware(...middlewares) {
return (next) => (reducer, initialState) => {
var store = next(reducer, initialState);
var dispatch = store.dispatch;
var chain = [];

var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);

return {
...store,
dispatch
};
};
}

这就是redux里面这个方法的源码,其中还一半是注释有木有。。。本来以为肯定有百来行代码的
当然这里不得不说es6的特性提供了非常多的帮助,所以为了省力吧es6玩透还是灰常有必要的(更别说为了装X了(^__^) )

从这里开始代码就有点绕了,我们逐行分析

1
return (next) => (reducer, initialState) => {...}

整个applyMiddleware方法就是返回了一个方法,根据applyMiddleware方法的使用,我们可以知道next就是createStore方法,
因为最终我们要返回的是一个装饰过的createStore方法,那么接收的参数肯定是不会变,所以最终我们调用createStoreWithMiddleware方法其实就是调用

1
2
3
4
function (reducer, initialState) {
var store = next(reducer, initialState); // next即为最初的createStore方法
// ...以下省略
}

1
2
3
var store = next(reducer, initialState);
var dispatch = store.dispatch;
var chain = [];

这里没什么好讲的,首先创建了一个store,这个store就是最原始的通过createStore创建的store,后两行只是变量赋值

1
2
3
4
5
6
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);

这里是关键,必须详细进行讲解。

首先,这边声明了一个middlewareAPI对象,这个对象包含两个方法:

  1. getState:store中的getState方法的引用
  2. dispatch:对本身的dispatch方法进行一次封装

然后

1
chain = middlewares.map(middleware => middleware(middlewareAPI));

我们来仔细看看这行代码,首先我们对所有的中间件进行一个map,map结果就是调用中间件方法,将middlewareAPI作为参数传入,
这里我们拿redux-thunk中间件举例,来看看一个中间件是长什么样子的,传入的参数又是用来干嘛的。

1
2
3
4
5
6
export default function thunkMiddleware({ dispatch, getState }) {
return next => action =>
typeof action === 'function' ?
action(dispatch, getState) :
next(action);
}

redux-thunk的功能是让action支持异步,让我们可以在action中跟服务器进行交互等操作,而他的实现。。。(⊙﹏⊙)b是的,又是这么几行代码。

我们回顾之前的代码,在map所有中间件的时候我们调用了thunkMiddleware方法,传入两个方法dispatchgetState,然后返回了一个方法,
我们大致抽象一下,应该如下:

1
2
3
4
5
6
7
8
9
function (next) {

return function (action) {
typeof action === 'function' ?
action(dispatch, getState) :
next(action)
}

}

于是我们接下去分析applyMiddleware里面的代码,

1
chain = middlewares.map(middleware => middleware(middlewareAPI));

现在我们知道chain是一个数组,每一项是调用每个中间件之后的返回函数

1
dispatch = compose(...chain)(store.dispatch);

compose是redux里面的一个帮助函数,代码如下:

1
2
3
export default function compose(...funcs) {
return arg => funcs.reduceRight((composed, f) => f(composed), arg);
}

~~(>_<)~~我已经不想再吐槽什么了,

我们看到这边先调用了compose函数,传入了结构后的chain数组,然后compose函数返回的也是一个函数:

1
2
3
4
function (arg) {
return funcs.reduceRight((composed, f) => f(composed), arg);
// funcs就是中间件数组
}

然后我们把store.dispatch函数作为arg传入这个结果,这里reduceRight可以参考这里
。那么这边得到的结果是什么呢?

1
2
// 假设中间件数组是[A, B, C]
// 那么结果就是A(B(C(store.dispatch)))

再次结合redux-thunk来看,我们假设只有一个中间件,那么最终的dispatch方法就是

1
2
3
4
5
6
7
function (action) {
typeof action === 'function' ?
action(dispatch, getState) :
next(action)
}
// 这里的next方法,就是真正的store.dispatch方法
// 这里的dispatch是(action) => store.dispatch(action)

我们再结合redux-thunk的使用方法来分析一下,

1
2
3
4
5
6
7
8
function incrementAsync() {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}

这是使用redux-thunk时可以定义的异步action,我们触发action的时候调用的是

1
dispatch(incrementAsync())

incrementAsync返回的是

1
2
3
4
5
6
function (dispatch) {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
}

这个时候我们回想经过中间件加工的dispatch方法:

1
2
3
4
5
6
7
function (action) {
typeof action === 'function' ?
action(dispatch, getState) :
next(action)
}
// 这里的next方法,就是真正的store.dispatch方法
// 这里的dispatch是(action) => store.dispatch(action)

action是一个函数,所以action === 'function' ?成立,那么就执行action, 并把中间件接收到的dispatch方法((action) => store.dispatch(action))方法作为参数传入,
在异步方法执行完之后再次触发真正的action。如果action不是异步的,那么久直接返回一个对象,这个时候action === 'function' ?不成立,就直接调用next
也就是原始的store.dispatch方法。

我们再接着想,如果我们有许多个中间件,那么没一个中间件的next就是下一个中间件直到最后一个中间件调用store.dispatch为止。

以上的代码非常绕,建议去专研一下源码。这么精简的代码包含了非常多的函数式编程的思想,也用到了装饰器模式的原理,不得不说:

太烧脑啦/(ㄒoㄒ)/~~