Redux的基础知识前几篇该介绍的都介绍完了,现在介绍点高阶的内容。本篇介绍一下中间件。我最早接触到中间件是在学习Express框架时,可见中间件Middleware并不是个什么新事物。中间件要满足两个特性:一是扩展功能,二是可以被链式组合。
我们来看个例子,例如Redux里dispatch一个Action,Reducer收到Action后更新state。我们希望能在这个过程中自动打印出Action对象和更新后的state,便于调试和追踪数据变化流。现在我们来写一个logger的中间件。源码已上传Github,请参照src/reactReduxMiddleware文件夹。
首先考虑在什么时候打印log?回顾前几篇介绍的Redux基础知识,Action是个plan object没地方写console.log。Action creator里可以打印出Action对象,但此时还没有执行Reducer,因此无法打印更新后的state。Reducer里可以取到Action对象和更新后的state,但它应该是个纯函数,不应该把console.log代码写进去。最后只剩一个选择,写到调用dispatch的地方:
console.log('current state: ', store.getState());
console.log('dispatching', action);
store.dispatch(action);
console.log('next state', store.getState());
这样能顺利打印出log,功能实现了,目的达到了。但显然我们可以封装一下,在所有dispatch前后都自动打印一下log。写一个增强版Store.dispatch:
const preDispatch = store.dispatch;
store.dispatch = (action) => {
console.log('current state: ', store.getState());
console.log('action: ', action);
preDispatch(action);
console.log('next state: ', store.getState());
};
上面都是自解释代码,函数签名与返回值和原生Store.dispatch一模一样,函数内部仍旧调用原生的Store.dispatch,最后将Store.dispatch指向这个增强版。在程序入口处加上这段代码,程序员写业务代码时根本意识不到这是个增强版dispatch方法,仿佛dispatch本就应该打印出log一样。
这就是中间件的雏形,Redux里的中间件都是改写dispatch方法(原因上面说了,Redux里只有dispatch适合写中间件)。但如果有多个中间件,都是改了dispatch方法,该怎么处理呢?
以上述打印log为例,简单起见,我们将其拆成两个。一个中间件只打印出Action,另一个中间件只打印出更新后的state:
// 只打印出 Action
export const loggerAction = (store) => {
const preDispatch = store.dispatch;
store.dispatch = (action) => {
console.log('dispatching', action);
const result = preDispatch(action);
return result;
};
};
// 只打印出 更新后的state
export const loggerState = (store) => {
const preDispatch = store.dispatch;
store.dispatch = (action) => {
const result = preDispatch(action);
console.log('next state', store.getState());
return result;
};
};
const store = createStore(reducer);
loggerAction(store);
loggerState(store);
顺利打出log,效果如图:
打上两个补丁的最终Store.dispatch如图,像个洋葱圈:
// 只打印出 Action
export const loggerAction = (store) => (dispatch) => (action) => {
console.log('dispatching', action);
const result = dispatch(action);
return result;
};
// 只打印出 更新后的state
export const loggerState = (store) => (dispatch) => (action) => {
const result = dispatch(action);
console.log('next state', store.getState());
return result;
};
export const applyMiddleware = (store, middlewares) => {
let dispatch = store.dispatch;
middlewares.forEach((middleware) => {
dispatch = middleware(store)(dispatch);
});
return {
...store,
dispatch,
};
};
entries/reactReduxMiddleware.js:
let store = createStore(reducer);
store = applyMiddleware(store, [loggerAction, loggerState]);
分析一下上面的代码,如果你熟悉ES6的箭头函数,其实也是自解释代码。原理就是将每个中间件设计成接受一个dispatch参数,并返回加工过的dispatch作为下一个中间件的参数,以方便链式调用。在applyMiddleware中返回的是原生store的一个副本,副本里的dispatch被最终生成的洋葱圈式的dispatch替换了。
这个applyMiddleware已经很接近Redux官方提供的同名方法了,只是官方版更加优化,将第一个参数store也封装掉,返回的是一个createStore方法。参照lib/middleware.js的final Step:
export const applyMiddleware = (...middlewares) => {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer);
let dispatch = store.dispatch;
middlewares.forEach((middleware) => {
dispatch = middleware(store)(dispatch);
});
return {
...store,
dispatch,
};
};
};
entries/reactReduxMiddleware.js:
const createStoreWithMiddleware = applyMiddleware(loggerAction, loggerState)(createStore);
const store = createStoreWithMiddleware(reducer);
上面这个版本的applyMiddleware和官方版的源码相似度已经达到90%,只是写法细节上稍有不同。
最后总结一下,中间件是一个强化源码功能,支持多个中间件链式调用的概念。在Redux里中间件等同于修改Store.dispatch方法。多个中间件用applyMiddleware方法组合成一个洋葱圈式的强化版Store.dispatch。常用的中间件有redux-logger,及下一篇异步Action中会介绍到的redux-thunk。