middleware 的演进史:为什么我们需要Middleware
参考:http://www.redux.org.cn/docs/advanced/Middleware.html
在Express或者Koa等服务端框架中,middleware 是指可以嵌入在框架接受请求到产生相应过程之中的代码。例如,Express、Koa 的 middleware 可以完成添加 CROS headers 、记录日志、内容压缩等工作。middleware 最优秀的特性就是可以被链式组合。
Redux 中的middleware被用于解决不同的问题,但其中的概念是类似的。他提供的是位于action被发起之后,到达reducer之前的扩展点。
演进1:实现记录每一次action及之后的state
//monkeypatching
//用 next 变量来保存原有的store.dispatch
//然后将store.dispatch用带有定制功能的dispatchAndLog替代
//dispatchAndLog 中最终用next(即原始的store.dispatch)派发action
let next = store.dispatch;
store.dispatch = function dispatchAndLog(action){
console.log('dispatching',action);
let result = next(action);
console.log('next state' ,store.getState())
return result;
}
演进1基本实现了记录action和state的功能。但有一个问题,当我们需要添加崩溃报告的时候(即不仅要记录每一次action和state,当出现错误时及时报告)怎么办?
演进2:增加崩溃报告
日志记录和崩溃报告分离是很重要的。理性情况下我们希望它们是两个不同的模块,也可能在不同的包中。
function patchStoreToAddLogging(store){
let next = store.dispatch;
store.dispatch = function dispatchAndLog(action){
console.log('dispatching',action);
let result = next(action);
console.log('next state' ,store.getState())
return result;
}
}
function patchStoreToAddReporting(store){
let next = store.dispatch;
store.dispatch = function dispatchAndReportErrors(action){
try{
return next(action);
}catch(err){
console.log('捕获异常:',err);
Raven.captureException(err,{
extra:{
action,
state:store.getState()
}
});
throw err;
}
}
}
使用:
patchStoreToAddLogging(store)
patchStoreToAddCrashReporting(store)
这样实质上是完成了一个功能上的链式串联:
当上述两个函数执行完成之后,store.dispatch 为 dispatchAndReportErrors;next 为dispatchAndLog。因而最终在应用中调用dispatch时,实际上是执行的一串链式调用:dispatchAndReportErrors -> dispatchAndLog ->(origin) dispatch.

图片来源:https://segmentfault.com/a/1190000012653724
从上也可以看出middleware的调用有一定的顺序性。
演进2可以看出使用时总是需手动调用两个函数,麻烦。
演进3:返回dispatch函数
function logger(store){
let next = store.dispatch;
return function dispatchAndLog(action){
console.log('dispatching',action);
let result = next(action);
console.log('next state' ,store.getState())
return result;
}
}
function applyMiddlewareByMonkeypatching(store,middlewares){
middlewares = middlewares.slice();
middlewares.reverse();
middlewares.forEach(middleware=>
//替换掉原始的dispatch
store.dispatch = middleware(store)
)
}
使用:
applyMiddlewareByMonkeypatching(store,[logger,crashReporter]);
演进4:以上每次改变 store.dispatch 的赋值实质上是为了实现链式化调用,可以不使用 store.dispatch 作中间桥梁,而直接通过嵌套函数来解决。
const logger = store => next => action => {
console.log('dispatching',action);
let result = next(action);
console.log('next state',store.getStore());
return result;
}
const crashReporter = store => next => action =>{
try{
return next(action);
}catch(err){
console.error('Caught an exception!',err);
Raven.captureException(err,{
extra:{
action,
state:state.getState()
}
})
throw err;
}
}
//注意:这不是redux applyMiddleware的实现
//但是非常接近
function applyMiddleware(store,middlewares){
middlewares = middlewares.slice();
middlewares.reverse();
let dispatch = store.dispatch;
middlewares.forEach(middleware =>
dispatch = middleware(store)(dispatch);
)
return Object.assign({},store,{dispatch});
}
Middleware 接收了一个
next()的 dispatch 函数,并返回一个 dispatch 函数,返回的函数会被作为下一个 middleware 的next(),以此类推。由于 store 中类似getState()的方法依旧非常有用,我们将store作为顶层的参数,使得它可以在所有 middleware 中被使用。
演进5:redux 的Middleware
演进4中我们写的两个Middleware可以在redux中这么使用:
import {createStore,combineReducers,applyMiddleWare} from 'redux';
let reducer = combineReducers(reducers);
let store = createStore(
reducer,
applyMiddleware(logger,crashReporter)
)
中间件的特点
- 中间件是独立的函数:中间件之间不应该有依赖关系,每个中间件应该能够独立被一个Redux Store 使用,完成某一特定功能。
- 中间件可以组合使用:
- 中间件有一个统一的接口:
//一个什么都不做的中间件
function doNothingMiddleware({dispatch,getState}){
return function(next){
return function(action){
return next(action);
}
}
}
//使用es6表示法看起来更舒服一些
const doNothingMiddleware = store => next => action => {
next(action);
}
参见doNothingMidddleware中间件,为什么一个中间件的接口定义需要这么多层函数能?(外层包裹+内3层return)。
Redux是根据函数式编程思想来设计的,函数式编程的一个重要思想就是让每个函数的工能尽可能的小,然后通过函数的嵌套组合来实现复杂的功能。