Redux作为目前最火的Flux
模式实现之一,它有很多的点值得研究。今天我们首先来看看它的Middleware。
熟悉Express
或者koa
的朋友对Middleware的概念一定不陌生。例如Express中是这样使用一个中间件的:
var app = express();
app.use(function(req, res, next) {
console.log('%s %s', req.method, req.url);
next();
});
app.use
中的方法,可以在其后面的http VERB
调用之前,对request
对象和response
对象进行处理,然后通过调用next
方法将处理过程转发到下一中间件或者通过返回响应来结束处理过程。(之后有机会的话再写一写Node
和Express
)。
我理解的所谓中间件其实就是,通过类似装饰者模式的形式,用代码预处理的方式,保证原本处理问题的函数(或方法)调用不变。
Redux中的中间件可以使得在用户调用store.dispatch
之后,先对参数state
和actions
进行预处理,再让真正的store.dispatch
调用,以确保reducer
的纯度(函数式编程的概念)不变。
Redux中提供了applyMiddleware
方法,它的源码只有十几行,真的是非常精妙。
下面我们就研究一下它的源代码。
<!–more–>
applyMiddleware方法
applyMiddleware(...middlewares){
return next => (reducer, initialState){
var store = next(reducer, initialState),
dispatch = store.dispatch,
chain = [],
middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
chain = middlewares.map(middleware =>
middleware(middlewareAPI));
dispatch = compose(...chain, store.dispatch);
return {
...store,
dispatch
}
}
}
这段代码的意思就是,appleMiddleware
方法接收一个Middleware
列表,以applyMiddleware(middleware1, middleware2, middleware3)
的形式调用(参见ES6的rest参数语法),然后再将创建store的方法传入(我想这个原因是Redux不仅仅可以在React中使用,也可以适用于任何Flux模式的框架和库),然后就会发生神奇的事情。
这两次调用(假设:var newCreateSore = applyMiddleware(middleware1, middleware2)(createStore)
)会产生一个新的创建Store的方法,但是它改造了原本Store的dispatch
方法,让这个dispatch
可以做原生dispatch
不能做的事情,这样我们就可以订制dispatch
的行为,从而实现了中间件的概念。
故而,newCreateStore
将作为createStore
的替代方法,使用newCreateStore
会产生带有中间件的store。
在最内层是如何实现中间件的调用的呢?让我们继续研究。
首先我们用传入的next
(一个可以创建Store的函数),创建一个原始的store,并且取出其原生的store.dispatch
方法和store.getState
方法成为一个对象,作为参数传入中间件函数中,让其第一次包装这个类似store的对象,并返回新的函数。
然后我们使用compose
函数,将这些包装过后的返回的函数一个接一个的嵌套调用。
这里补充一下compose的概念:
假设有若干函数f1, f2, f3…,compose指的是类似f1(f2(f3(x)))的调用方式,这在函数式编程中很常见。
(这里的compose
函数是redux中的一个方法,这里我们不上它的源码,有兴趣的朋友可以直接看源码。)
被嵌套在compose最内层的是原生的store.dispatch
方法,这里我们就一层层的将其包装,在中间件函数中,我们可以利用store的其他方法,比如store.dispatch
和store.getState
,做一些有意思的事情,比如实现一个记录state
改变的日志中间件。
中间件函数
从上面的分析中,我们不难写一个符合要求的中间件函数。
首先中间件函数需要接受一个middlewareAPI
,如果使用ES6的语法,这里可以看成是接收一个{dispatch, getState}
的形式的参数,这样我们就能在内层使用这两个方法。
接收middlewareAPI
参数之后,中间件函数返回另一个函数(为方便后面解释,假设返回的函数为dispatch n
)。这个函数既然要用于compose,也就是说它接收一个形式为dispatch
的函数,对其一层层嵌套(形式为dispatch1(dispatch2(dispatch3(dispatch)))
)。在其内部我们可以在之前的dispatch
调用之前和之后,进行一些逻辑的处理。
写一个简单的记录state日志的中间件如下:
var middlewareLogger = ({getState}) => next => action => {
console.log(getState());
next(action);
console.log(getState());
}
怎么样,是不是特别简单?
再写一个异步操作的中间件:
const readyStatePromise = store => next => action => {
if (!action.promise) {
return next(action)
}
function makeAction(ready, data) {
let newAction = Object.assign({}, action, { ready }, data)
delete newAction.promise
return newAction
}
next(makeAction(false))
return action.promise.then(
result => next(makeAction(true, { result })),
error => next(makeAction(true, { error }))
)
}
这个中间件让你可以发起带有一个 { promise } 属性的特殊 action。这个 middleware 会在开始时发起一个 action,并在这个 promise
resolve 时发起另一个成功(或失败)的 action。为了方便起见,dispatch
会返回这个 promise 让调用者可以等待。