Redux也有Middleware的概念,类似于Express或者Koa。事实上,Redux的中间件模型更接近Koa的,都是洋葱模型,而Express的中间件模型则是瀑布流模型。
官方文档对中间件已经有了比较详细的解释。或者参考这个非常简单的例子来初步了解Redux中间件的使用。
下面通过对Redux的部分源码进行分析,来详细了解Redux的中间件机制。
1. compose
Redux中间件机制的核心方法是applyMiddleware
,但是在了解该方法之前,先来分析依赖函数compose
。源码如下:
export default function compose(...funcs) {
return (...args) => {
if (funcs.length === 0) {
return args[0]
}
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return rest.reduceRight((composed, f) => f(composed), last(...args))
}
}
该方法接收一系列的函数作为参数,返回值是一个新的函数。其效果相当于函数的嵌套调用。例如:
compose(f, g, h)(...args)
就相当于:
f(g(h(...args)))
2. applyMiddleware
源码及分析如下:
export default function applyMiddleware(...middlewares) {
/**
* 返回值是一个函数,因此在使用的时候是如下形式:
* var store = applyMiddleware(...midlewares)(createStore)(reducer)
*/
return (createStore) => (reducer, initialState, enhancer) => {
// 普通方式创建的store
var store = createStore(reducer, initialState, enhancer)
var dispatch = store.dispatch
var chain = []
/**
* 将store的一部分接口暴露给中间件
* 在编写中间件的时候,通常形式为:
* function middleware(store){
* return next => action => { ... }
* }
* 参数的store其实就是middlewareAPI
*/
var middlewareAPI = {
getState: store.getState,
/**
* 这里之所以使用一个匿名函数,而不是`dispatch: dispatch`的形式
* 是因为在中间件中执行store.dispatch的时候,dispatch的值已经改变
* 在下面的代码中将会看到dispatch被重新赋值
*/
dispatch: (action) => dispatch(action)
}
/**
* chain是一个数组,数组中的每一项都是一个具有如下形式的函数:
* function(next){
* return function(action){ ... }
* }
*/
chain = middlewares.map(middleware => middleware(middlewareAPI))
/**
* 假设chain为[mw1, mw2, mw3]
* 那么此时dispatch为mw1(mw2(mw3(store.dispatch)))
* 即store.dispatch作为mw3的next参数
* mw3(store.dispatch)作为mw2的next参数,以此类推
* 最终的返回值是一个函数,其形式为:
* function(action){ ... }
* 该函数作为新的dispatch(或者说,包装后的dispatch)
*/
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
3. 实例
假如有两个中间件,logger
和greeting
:
function logger(store) {
return function(next) {
return function(action) {
console.log('logger: dispatching:', action)
var result = next(action)
console.log('logger: get state:', store.getState())
return result
}
}
}
function greeting(store) {
return function(next) {
return function(action) {
console.log('greeting: Hi~ o(* ̄▽ ̄*)ブ')
var result = next(action)
console.log('greeting: ヾ( ̄▽ ̄)Bye~Bye~')
return result
}
}
}
那么经过applyMiddleware
后,新的dispatch
函数其实是:
function dispatch(action) {
console.log('logger: dispatching:', action)
var result = (function(action) {
console.log('greeting: Hi~ o(* ̄▽ ̄*)ブ')
var result = store.dispatch(action)
console.log('greeting: ヾ( ̄▽ ̄)Bye~Bye~')
return result
})(action)
console.log('logger: get state:', store.getState())
return result
}
如果有更多的中间件的话,这个过程会一层一层嵌套下去。
通过对这个例子的分析,可以非常直观地看出,中间件的作用,无非是捕获store.dispatch(action)
这个动作,在它的之前和之后做一些其它事情。
4. 异步Action
通过前面的源码分析,我们可以知道,每个中间件所接收到的store
参数,其实是真正Store的一个子集,只有dispatch
和getState
方法。那么,如果在某个中间件中调用了dispatch(action)
,岂不是就陷入无限循环了?事实上,中间件拿到dispatch
,主要是用于异步操作。
下面看一个例子(完整源码)。
// action.js
function increaseCounterAsync() {
return function(dispatch, getState) {
setTimeout(() => dispatch({
type: 'INCREASE'
}), 3000)
}
}
// middleware: thunk
function thunk(store) {
return function(next) {
return function(action) {
return typeof action === 'function' ?
action(store.dispatch, store.getState) :
next(action)
}
}
}
// store
var store = applyMiddleware(thunk, logger)(createStore)(reducer)
store.dispatch(increaseCounterAsync())
注: 这里的thunk中间件其实就是redux-thunk的实现。
在这里,increaseCounterAsync()
创建的action是一个函数,而thunk中间件会对action进行拦截,如果action是一个函数,则将dispatch
和getState
作为参数执行该函数,否则将该action按照普通流程来处理。也就是说,thunk中间件捕获到了异步action之后,进行一些处理,然后dispatch真正的action。
在上面的例子中,使用了thunk和logger两个中间件,此时dispatch
函数其实是:
function dispatch(action) {
if (typeof action === 'function') {
// 这里的dispatch和getState其实是middlewareAPI的
return action(dispatch, getState)
}
var result = (function(action) {
console.log('logger: dispatching:', action)
// 这里的store指的是applyMiddleware的内部变量store
var result = store.dispatch(action)
console.log('logger: get state:', store.getState())
return result
})(action)
return result
}
可以看到,如果一个异步action的话,被thunk中间件捕获后,就不会执行到后面的logger部分。然后action(dispatch, getState)
会重新dispatch一个同步action。