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) ); // ...@H_502_15@加载中间件有两个核心的方法:
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 } } }@H_502_15@这就是
applyMiddleware
方法的全部内容,我们细剖来看。首先,applyMiddleware
方法接收一个或多个中间件作为参数(会被函数作为ES6的rest
参数读取,变成一个数组),然后返回了一个匿名函数:return (createStore) => (reducer,enhancer) => { ... }@H_502_15@这种写法同样是
ES6
的写法,翻译成ES5
其实就是:return function (createStore) { return function (reducer,enhancer) { ... } };@H_502_15@也就是说,负责加载中间件的
applyMiddleware
方法其实只是返回了一个带有一个入参的匿名函数。此时,createStore
方法执行的时候即为:let store = createStore( reducers,defaultReducer,function (createStore) {...} // applyMiddleware(thunk) );@H_502_15@接下来就来看看
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 } }@H_502_15@
createStore
函数接收三个参数:
-
reducer
:即我们通过combineReducers
导出的reducer
集合; -
preloadedState
:可选参数,初始化的state
; -
enhancer
:用来增强store
,也就是通过applyMiddleware
返回的匿名函数。
逐块分析代码:
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined }@H_502_15@这块代码来处理
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) }@H_502_15@这块代码其实是 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) { // 第二层匿名函数 ... } };@H_502_15@实际上,
enhancer(createStore)(reducer,preloadedState)
执行的时候,参数createStore
给了第一层匿名函数,因为我们的目的是要对 createStore 进行修饰。而reducer
,preloadedState
两个参数给了第二层匿名函数。第二层匿名函数同样拥有 reducer,enhancer 三个参数,也即:
// 接收了 enhancer(createStore) 传来的 reducer,preloadedState return function (reducer,enhancer) { // 第二层匿名函数 var store = createStore(reducer,dispatch } }@H_502_15@那我们就来看一看这个匿名函数又做了什么事情。
var store = createStore(reducer,enhancer)@H_502_15@首先,第二层匿名函数又调了
createStore
方法(又回去了…orz)。刚才也说到,在我们应用入口调createStore
方法的时候,第三个参数enhancer
其实传的是我们注册的中间件。而这时,createStore
接收到的参数只有reducer
和preloadedState
,也就是说会按照正常的没有注册中间件的情况,直接往下执行然后返回dispatch
,getState
等等这些东西。所以这时候store
拿到的是:return { dispatch,[$$observable]: observable }@H_502_15@接着往下看。
var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState,dispatch: (action) => dispatch(action) }@H_502_15@这些就是正常的变量赋值。继续往下。
chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch)@H_502_15@别忘了,我们目前执行的第一层匿名函数和第二层匿名函数,都是在
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); } } }@H_502_15@是的,
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)) } }@H_502_15@
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);@H_502_15@以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)))@H_502_15@明白了
compose
方法,我们就假设只有一个中间件,dispatch
的值就等于:function(next) { return function(action) { return typeof action === 'function' ? action(dispatch,getState) : next(action); } }(store.dispatch)@H_502_15@也就是说,其实
next
参数就等于store.dispatch
。而此时,dispatch
就等于:dispatch = function(action) { return typeof action === 'function' ? action(dispatch,getState) : next(action); }@H_502_15@我们结合
redux-thunk
的用法来分析这个中间件是如何运行的。// 异步的 action function incrementAsync() { return dispatch => { setTimeout(() => { dispatch(increment()); },1000); }; }@H_502_15@触发上面异步 action 的方式是:
dispatch(incrementAsync());@H_502_15@回想上面的代码,
dispatch
方法是接收一个action
参数的函数,而这里的action
就是incrementAsync()
,进入dispatch
方法之后,就是:return typeof action === 'function' ? action(dispatch,getState) : next(action);@H_502_15@当
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) // 进入中间件分支 }@H_502_15@当中间件分支处理完
store
以后,就又回到了主干。这种方式其实是使用了装饰者模式,通过不同的中间件对createStore
进行修饰,形成最后的新的createStore
方法,这样一来,通过这个方法创建的store
就拥有了中间件的处理结果。过程的确是比较绕的,但把源码和中间件的用法结合起来看的话,其实也就不难理解了。