中间件 middleware 是 redux 中最难理解的地方。但是我挑战一下用最通俗的语言来讲明白它。如果你看完这一小节,还没明白中间件是什么,不知道如何写一个中间件,那就是我的锅了!

中间件是对 dispatch 的扩展,或者说重写,增强 dispatch 的功能!

记录日志

我现在有一个需求,在每次修改 state 的时候,记录下来 修改前的 state ,为什么修改了,以及修改后的 state。我们可以通过重写 store.dispatch 来实现,直接看代码

  1. const store = createStore(reducer);
  2. const next = store.dispatch;
  3. /*重写了store.dispatch*/
  4. store.dispatch = (action) => {
  5. console.log('this state', store.getState());
  6. console.log('action', action);
  7. next(action);
  8. console.log('next state', store.getState());
  9. }

我们来使用下

  1. store.dispatch({
  2. type: 'INCREMENT'
  3. });

日志输出为

  1. this state { counter: { count: 0 } }
  2. action { type: 'INCREMENT' }
  3. 1
  4. next state { counter: { count: 1 } }

现在我们已经实现了一个完美的记录 state 修改日志的功能!

记录异常

我又有一个需求,需要记录每次数据出错的原因,我们扩展下 dispatch

  1. const store = createStore(reducer);
  2. const next = store.dispatch;
  3. store.dispatch = (action) => {
  4. try {
  5. next(action);
  6. } catch (err) {
  7. console.error('错误报告: ', err)
  8. }
  9. }

这样每次 dispatch 出异常的时候,我们都会记录下来。

多中间件的合作

我现在既需要记录日志,又需要记录异常,怎么办?当然很简单了,两个函数合起来呗!

  1. store.dispatch = (action) => {
  2. try {
  3. console.log('this state', store.getState());
  4. console.log('action', action);
  5. next(action);
  6. console.log('next state', store.getState());
  7. } catch (err) {
  8. console.error('错误报告: ', err)
  9. }
  10. }

如果又来一个需求怎么办?接着改 dispatch 函数?那再来10个需求呢?到时候 dispatch 函数肯定庞大混乱到无法维护了!这个方式不可取呀!

我们需要考虑如何实现扩展性很强的多中间件合作模式。

  1. 我们把 loggerMiddleware 提取出来
  1. const store = createStore(reducer);
  2. const next = store.dispatch;
  3. const loggerMiddleware = (action) => {
  4. console.log('this state', store.getState());
  5. console.log('action', action);
  6. next(action);
  7. console.log('next state', store.getState());
  8. }
  9. store.dispatch = (action) => {
  10. try {
  11. loggerMiddleware(action);
  12. } catch (err) {
  13. console.error('错误报告: ', err)
  14. }
  15. }
  1. 我们把 exceptionMiddleware 提取出来
  1. const exceptionMiddleware = (action) => {
  2. try {
  3. /*next(action)*/
  4. loggerMiddleware(action);
  5. } catch (err) {
  6. console.error('错误报告: ', err)
  7. }
  8. }
  9. store.dispatch = exceptionMiddleware;
  1. 现在的代码有一个很严重的问题,就是 exceptionMiddleware 里面写死了 loggerMiddleware,我们需要让 next(action)变成动态的,随便哪个中间件都可以
  1. const exceptionMiddleware = (next) => (action) => {
  2. try {
  3. /*loggerMiddleware(action);*/
  4. next(action);
  5. } catch (err) {
  6. console.error('错误报告: ', err)
  7. }
  8. }
  9. /*loggerMiddleware 变成参数传进去*/
  10. store.dispatch = exceptionMiddleware(loggerMiddleware);
  1. 同样的道理,loggerMiddleware 里面的 next 现在恒等于 store.dispatch,导致 loggerMiddleware 里面无法扩展别的中间件了!我们也把 next 写成动态的
  1. const loggerMiddleware = (next) => (action) => {
  2. console.log('this state', store.getState());
  3. console.log('action', action);
  4. next(action);
  5. console.log('next state', store.getState());
  6. }

到这里为止,我们已经探索出了一个扩展性很高的中间件合作模式!

  1. const store = createStore(reducer);
  2. const next = store.dispatch;
  3. const loggerMiddleware = (next) => (action) => {
  4. console.log('this state', store.getState());
  5. console.log('action', action);
  6. next(action);
  7. console.log('next state', store.getState());
  8. }
  9. const exceptionMiddleware = (next) => (action) => {
  10. try {
  11. next(action);
  12. } catch (err) {
  13. console.error('错误报告: ', err)
  14. }
  15. }
  16. store.dispatch = exceptionMiddleware(loggerMiddleware(next));

这时候我们开开心心的新建了一个 loggerMiddleware.js,一个exceptionMiddleware.js文件,想把两个中间件独立到单独的文件中去。会碰到什么问题吗?

loggerMiddleware 中包含了外部变量 store,导致我们无法把中间件独立出去。那我们把 store 也作为一个参数传进去好了~

  1. const store = createStore(reducer);
  2. const next = store.dispatch;
  3. const loggerMiddleware = (store) => (next) => (action) => {
  4. console.log('this state', store.getState());
  5. console.log('action', action);
  6. next(action);
  7. console.log('next state', store.getState());
  8. }
  9. const exceptionMiddleware = (store) => (next) => (action) => {
  10. try {
  11. next(action);
  12. } catch (err) {
  13. console.error('错误报告: ', err)
  14. }
  15. }
  16. const logger = loggerMiddleware(store);
  17. const exception = exceptionMiddleware(store);
  18. store.dispatch = exception(logger(next));

到这里为止,我们真正的实现了两个可以独立的中间件啦!

现在我有一个需求,在打印日志之前输出当前的时间戳。用中间件来实现!

  1. const timeMiddleware = (store) => (next) => (action) => {
  2. console.log('time', new Date().getTime());
  3. next(action);
  4. }
  5. ...
  6. const time = timeMiddleware(store);
  7. store.dispatch = exception(time(logger(next)));

本小节完整源码见 demo-6

中间件使用方式优化

上一节我们已经完全实现了正确的中间件!但是中间件的使用方式不是很友好

  1. import loggerMiddleware from './middlewares/loggerMiddleware';
  2. import exceptionMiddleware from './middlewares/exceptionMiddleware';
  3. import timeMiddleware from './middlewares/timeMiddleware';
  4. ...
  5. const store = createStore(reducer);
  6. const next = store.dispatch;
  7. const logger = loggerMiddleware(store);
  8. const exception = exceptionMiddleware(store);
  9. const time = timeMiddleware(store);
  10. store.dispatch = exception(time(logger(next)));

其实我们只需要知道三个中间件,剩下的细节都可以封装起来!我们通过扩展 createStore 来实现!

先来看看期望的用法

  1. /*接收旧的 createStore,返回新的 createStore*/
  2. const newCreateStore = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware)(createStore);
  3. /*返回了一个 dispatch 被重写过的 store*/
  4. const store = newCreateStore(reducer);

实现 applyMiddleware

  1. const applyMiddleware = function (...middlewares) {
  2. /*返回一个重写createStore的方法*/
  3. return function rewriteCreateStoreFunc(oldCreateStore) {
  4. /*返回重写后新的 createStore*/
  5. return function newCreateStore(reducer, initState) {
  6. /*1. 生成store*/
  7. const store = oldCreateStore(reducer, initState);
  8. /*给每个 middleware 传下store,相当于 const logger = loggerMiddleware(store);*/
  9. /* const chain = [exception, time, logger]*/
  10. const chain = middlewares.map(middleware => middleware(store));
  11. let dispatch = store.dispatch;
  12. /* 实现 exception(time((logger(dispatch))))*/
  13. chain.reverse().map(middleware => {
  14. dispatch = middleware(dispatch);
  15. });
  16. /*2. 重写 dispatch*/
  17. store.dispatch = dispatch;
  18. return store;
  19. }
  20. }
  21. }

让用户体验美好

现在还有个小问题,我们有两种 createStore 了

  1. /*没有中间件的 createStore*/
  2. import { createStore } from './redux';
  3. const store = createStore(reducer, initState);
  4. /*有中间件的 createStore*/
  5. const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);
  6. const newCreateStore = rewriteCreateStoreFunc(createStore);
  7. const store = newCreateStore(reducer, initState);

为了让用户用起来统一一些,我们可以很简单的使他们的使用方式一致,我们修改下 createStore 方法

  1. const createStore = (reducer, initState, rewriteCreateStoreFunc) => {
  2. /*如果有 rewriteCreateStoreFunc,那就采用新的 createStore */
  3. if(rewriteCreateStoreFunc){
  4. const newCreateStore = rewriteCreateStoreFunc(createStore);
  5. return newCreateStore(reducer, initState);
  6. }
  7. /*否则按照正常的流程走*/
  8. ...
  9. }

最终的用法

  1. const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);
  2. const store = createStore(reducer, initState, rewriteCreateStoreFunc);

本小节完整源码见 demo-7