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,enhancer) => { var store = createStore(reducer,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,enhancer) => { ... }
这种写法同样是 ES6
的写法,翻译成 ES5
其实就是:
return function (createStore) { return function (reducer,enhancer) { ... } };
也就是说,负责加载中间件的 applyMiddleware
方法其实只是返回了一个带有一个入参的匿名函数。此时,createStore
方法执行的时候即为:
let store = createStore( reducers,defaultReducer,function (createStore) {...} // applyMiddleware(thunk) );
接下来就来看看 createStore
做了什么。
createStore
同样先来看一看 createStore
的源码:
export default function createStore(reducer,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,enhancer 这三个参数的 createStore
:
// applyMiddleware(thunk) 返回的匿名函数 // 接收了 enhancer 传来的 createStore return function (createStore) { // 第一层匿名函数 // 接收了 enhancer(createStore) 传来的 reducer,preloadedState return function (reducer,enhancer) { // 第二层匿名函数 ... } };
实际上,enhancer(createStore)(reducer,preloadedState)
执行的时候,参数 createStore
给了第一层匿名函数,因为我们的目的是要对 createStore 进行修饰。而 reducer
, preloadedState
两个参数给了第二层匿名函数。
第二层匿名函数同样拥有 reducer,enhancer 三个参数,也即:
// 接收了 enhancer(createStore) 传来的 reducer,preloadedState return function (reducer,enhancer) { // 第二层匿名函数 var store = createStore(reducer,dispatch } }
那我们就来看一看这个匿名函数又做了什么事情。
var store = createStore(reducer,enhancer)
首先,第二层匿名函数又调了 createStore
方法(又回去了…orz)。刚才也说到,在我们应用入口调 createStore
方法的时候,第三个参数 enhancer
其实传的是我们注册的中间件。而这时,createStore
接收到的参数只有 reducer
和 preloadedState
,也就是说会按照正常的没有注册中间件的情况,直接往下执行然后返回 dispatch
,getState
等等这些东西。所以这时候 store
拿到的是:
return { dispatch,[$$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
就拥有了中间件的处理结果。过程的确是比较绕的,但把源码和中间件的用法结合起来看的话,其实也就不难理解了。