redux中间件
redux 是一个轻量级的数据流管理工具,主要解决了 component -> action -> reducer -> state 的单向数据流转问题。同时, redux 也提供了类似于 koa 和 express 的中间件(middleware)的概念,让我们可以介入数据从 action
到 reducer
之间的传递过程,从而改变数据流,实现如异步、数据过滤、日志上报等功能。
redux 的中间件是通过第三方插件的方式实现,本身源码也不是很多,我们就从源码来解读 redux 的中间件机制。
首先来看我们是如何加载一个中间件的,以 redux-thunk 为例:
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import reducers from './reducers.js';
let store = createStore(
reducers,
preloadedState,
applyMiddleware(thunk)
);
// ...
加载中间件有两个核心的方法: createStore
和 applyMiddleware
,接下来我们就从源码剖析,看 redux 中间件的运行原理到底是怎么样的。
applyMiddleware
首先看一下 applyMiddleware
的源码:
import compose from './compose'
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
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
}
}
}
这就是 applyMiddleware
方法的全部内容,我们细剖来看。首先, applyMiddleware
方法接收一个或多个中间件作为参数(会被函数作为ES6的 rest
参数读取,变成一个数组),然后返回了一个匿名函数:
return (createStore) => (reducer, preloadedState, enhancer) => {
...
}
这种写法同样是 ES6
的写法,翻译成 ES5
其实就是:
return function (createStore) {
return function (reducer, preloadedState, enhancer) {
...
}
};
也就是说,负责加载中间件的 applyMiddleware
方法其实只是返回了一个带有一个入参的匿名函数。此时,createStore
方法执行的时候即为:
let store = createStore(
reducers,
defaultReducer,
function (createStore) {...} // applyMiddleware(thunk)
);
接下来就来看看 createStore
做了什么。
createStore
同样先来看一看 createStore
的源码:
export default function createStore(reducer, preloadedState, enhancer) {
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
var currentListeners = []
var nextListeners = currentListeners
var isDispatching = false
function ensureCanMutateNextListeners() {...}
function getState() {return currentState;}
function subscribe(listener) {...}
function dispatch(action) {...}
function replaceReducer(nextReducer) {...}
function observable() {...}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
createStore
函数接收三个参数:
-
reducer
:即我们通过combineReducers
导出的reducer
集合; -
preloadedState
:可选参数,初始化的state
; -
enhancer
:用来增强store
,也就是通过applyMiddleware
返回的匿名函数。
逐块分析代码:
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
这块代码来处理 preloadedState
可选参数,处理是两个参数还是三个参数的情况,比较简单。简单概括就是,如果只传了两个参数,并且第二个参数为函数,第二个参数会被当作 enhancer
。
继续往下看:
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
这块代码其实是 redux 中间件的核心入口,也是有无中间件处理流程的分叉口。如果我们注册了中间件,就会执行 enhancer
,而如果没有注册的话,就直接往下执行然后返回 dispatch
, getState
等等这些东西了。我们来看注册中间件的情况下, enhancer
方法执行的时候发生了什么。
enhancer
就是上面讲过的 applyMiddleware
函数返回的匿名函数。 enhancer
方法接收一个参数: createStore
,你没看错,就是拥有 reducer, preloadedState, enhancer 这三个参数的 createStore
:
// applyMiddleware(thunk) 返回的匿名函数
// 接收了 enhancer 传来的 createStore
return function (createStore) { // 第一层匿名函数
// 接收了 enhancer(createStore) 传来的 reducer, preloadedState
return function (reducer, preloadedState, enhancer) { // 第二层匿名函数
...
}
};
实际上,enhancer(createStore)(reducer, preloadedState)
执行的时候,参数 createStore
给了第一层匿名函数,因为我们的目的是要对 createStore 进行修饰。而 reducer
, preloadedState
两个参数给了第二层匿名函数。
第二层匿名函数同样拥有 reducer, preloadedState, enhancer 三个参数,也即:
// 接收了 enhancer(createStore) 传来的 reducer, preloadedState
return function (reducer, preloadedState, enhancer) { // 第二层匿名函数
var store = createStore(reducer, preloadedState, enhancer)
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
}
}
那我们就来看一看这个匿名函数又做了什么事情。
var store = createStore(reducer, preloadedState, enhancer)
首先,第二层匿名函数又调了 createStore
方法(又回去了…orz)。刚才也说到,在我们应用入口调 createStore
方法的时候,第三个参数 enhancer
其实传的是我们注册的中间件。而这时,createStore
接收到的参数只有 reducer
和 preloadedState
,也就是说会按照正常的没有注册中间件的情况,直接往下执行然后返回 dispatch
, getState
等等这些东西。所以这时候 store
拿到的是:
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
接着往下看。
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)
别忘了,我们目前执行的第一层匿名函数和第二层匿名函数,都是在 applyMiddleware
方法的作用域内(都是 applyMiddleware 返回的匿名函数),所以可以直接访问 middlewares
参数。上面 chain
的值就是对中间件进行map,也就是调用中间件的方法。我们以 redux-thunk
为例,看一下 redux-thunk
的源码:
export default function thunkMiddleware({ dispatch, getState }) {
return function(next) {
return function(action) {
return typeof action === 'function' ?
action(dispatch, getState) :
next(action);
}
}
}
是的, redux-thunk
源码就这些。参数里的 dispatch, getState 就是我们在 map 的时候,调用 middleware
方法,传进来的 middlewareAPI
。所以我们知道了 chain
的值是一个数组,数组的每一项是调用每个中间件之后的返回函数。
我们再来看 dispatch
这一行发生了什么。这里有一个 compose
方法,来看一下源码:
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return function (...args) {
rest.reduceRight(function(composed, f) {
f(composed)
}, last(...args))
}
}
compose
类似于 Array
的 reduceRight
方法的处理方式,从数组最后一个数组依次向前处理。 如果不太熟悉,看下这个例子就会很快明白:
/**
* [description]
* @param {[type]} previousValue [前一个项]
* @param {[type]} currentValue [当前项]
*/
[0, 1, 2, 3, 4].reduceRight(function(previousValue, currentValue, index, array) {
return previousValue + currentValue;
}, 10);
以10为初始值,从数组的最后一位数字向左依次累加。所以结合上面的代码,可以知道 compose(...chain)
的运行结果是函数数组 chain
从最右边的元素开始,带上 store.dispatch
参数执行后依次作为前面一个函数的参数,类似下面这样:
A = function () {};
B = function () {};
C = function () {};
chain = [A, B, C];
//dispatch = compose(...chain)(store.dispatch)
dispatch = A(B(C(store.dispatch)))
明白了 compose
方法,我们就假设只有一个中间件,dispatch
的值就等于:
function(next) {
return function(action) {
return typeof action === 'function' ?
action(dispatch, getState) :
next(action);
}
}(store.dispatch)
也就是说,其实 next
参数就等于 store.dispatch
。而此时, dispatch
就等于:
dispatch = function(action) {
return typeof action === 'function' ?
action(dispatch, getState) :
next(action);
}
我们结合 redux-thunk
的用法来分析这个中间件是如何运行的。
// 异步的 action
function incrementAsync() {
return dispatch => {
setTimeout(() => {
dispatch(increment());
}, 1000);
};
}
触发上面异步 action 的方式是:
dispatch(incrementAsync());
回想上面的代码,dispatch
方法是接收一个 action
参数的函数,而这里的 action
就是 incrementAsync()
,进入 dispatch
方法之后,就是:
return typeof action === 'function' ?
action(dispatch, getState) :
next(action);
当 action
的值为 function
时,就调用这个 function ,并且把 dispatch, getState
传给这个 function ,如果不是 function ,就直接 store.dispatch(action)
(如上面所讲,next的值就是 store.dispatch
)。
那这是只有一个中间件的情况,有多个中间件时,next
就是下一个中间件,一直到调用到最后一个中间件为止。(脑袋已变成一锅粥/(ㄒoㄒ)/~~)
小结
回到我们最开始讲到的,redux 的中间件其实就是让我们可以介入到 action
和 reducer
之间的过程,我们可以把这个过程理解成主干和分支的概念,redux
默认的同步数据流就是主干,中间件就是分支,主干和分支的分水岭从这里时出现:
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState) // 进入中间件分支
}
当中间件分支处理完 store
以后,就又回到了主干。这种方式其实是使用了装饰者模式,通过不同的中间件对 createStore
进行修饰,形成最后的新的 createStore
方法,这样一来,通过这个方法创建的 store
就拥有了中间件的处理结果。过程的确是比较绕的,但把源码和中间件的用法结合起来看的话,其实也就不难理解了。