redux 是一个轻量级的数据流管理工具,主要解决了 component -> action -> reducer -> state 的单向数据流转问题。同时, redux 也提供了相似于 koa 和 express 的中间件(middleware)的概念,让咱们能够介入数据从 action
到 reducer
之间的传递过程,从而改变数据流,实现如异步、数据过滤、日志上报等功能。javascript
redux 的中间件是经过第三方插件的方式实现,自己源码也不是不少,咱们就从源码来解读 redux 的中间件机制。java
首先来看咱们是如何加载一个中间件的,以 redux-thunk 为例:react
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 中间件的运行原理究竟是怎么样的。express
首先看一下 applyMiddleware
的源码:redux
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
其实就是:app
return function (createStore) { return function (reducer, preloadedState, enhancer) { ... } };
也就是说,负责加载中间件的 applyMiddleware
方法其实只是返回了一个带有一个入参的匿名函数。此时,createStore
方法执行的时候即为:koa
let store = createStore( reducers, defaultReducer, function (createStore) {...} // applyMiddleware(thunk) );
接下来就来看看 createStore
作了什么。异步
一样先来看一看 createStore
的源码:ide
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
就拥有了中间件的处理结果。过程的确是比较绕的,但把源码和中间件的用法结合起来看的话,其实也就不难理解了。