# 中间件 middleware

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

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

# 记录日志

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

const store = createStore(reducer);
const next = store.dispatch;

// 重写了store.dispatch
store.dispatch = action => {
  console.log('this state', store.getState());
  console.log('action', action);
  next(action);
  console.log('next state', store.getState());
}

我们来使用下

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

日志输出为

this state { counter: { count: 0 } }
action { type: 'INCREMENT' }
1
next state { counter: { count: 1 } }

# 记录异常

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

const store = createStore(reducer);
const next = store.dispatch;

store.dispatch = action => {
  try {
    next(action);
  } catch (err) {
    console.error('错误报告: ', err);
  }
}

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

# 多中间件的合作

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

store.dispatch = action => {
  try {
    console.log('this state', store.getState());
    console.log('action', action);
    next(action);
    console.log('next state', store.getSatte());
  } catch (err) {
    console.error('错误报告: ', err);
  }
}

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

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

  1. 我们把loggerMiddleware提取出来

    const store = createStore(reducer);
    const next = store.dispatch;
    
    const loggerMiddleware = action => {
      console.log('this state', store.getState());
      console.log('action', action);
      next(action);
      console.log('next state', store.getState());
    }
    
    store.dispatch = action => {
      try {
        loggerMiddleware(action);
      } catch (err) {
        console.error('错误报告: ', err);
      }
    }
    
  2. 我们把exceptionMiddleware提取出来

    const exceptionMiddleware = action => {
      try {
        // next(action)
        loggerMiddleware(action);
      } catch (err) {
        console.error('错误报告: ', err);
      }
    }
    store.dispatch = exceptionMiddleware;
    
  3. 现在代码有一个很严重的问题,就是exceptionMiddleware里面写死了loggerMiddleware,我们需要让next(action变成动态的,随便哪个中间件都可以

    const exceptionMiddleware = next => action => {
      try {
        next(action);
      } catch (err) {
        console.error('错误报告: ', err);
      }
    }
    // loggerMiddleware 变成参数传进去
    store.dispatch = exceptionMiddleware(loggerMiddleware);
    
  4. 同样的道理,loggerMiddleware里面的next现在恒等于store.dispatch,导致loggerMiddleware里面无法扩展别的中间件了!我们也把next写成动态的

    const loggerMiddleware => next => action => {
      console.log('this state', store.getState());
      console.log('action', action);
      next(action);
      console.log('next state', store.getState());
    }
    

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

const store = createStore(reducer);
const next = store.dispatch;

const loggerMiddleware = next => action => {
  console.log('this state', store.getState());
  console.log('action', action);
  next(action);
  console.log('next state', store.getState());
}

const exceptionMiddleware = next => action => {
  try {
    next(action);
  } catch (err) {
    console.error('错误报告: ', err)
  }
}

store.dispatch = exceptionMiddleware(loggerMiddleware(next));

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

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

const store = createStore(reducer);
const next  = store.dispatch;

const loggerMiddleware = store => next => action => {
  console.log('this state', store.getState());
  console.log('action', action);
  next(action);
  console.log('next state', store.getState());
}

const exceptionMiddleware = store => next => action => {
  try {
    next(action);
  } catch (err) {
    console.error('错误报告: ', err)
  }
}

const logger = loggerMiddleware(store);
const exception = exceptionMiddleware(store);
store.dispatch = exception(logger(next));

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

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

const timeMiddleware = store => next => action => {
  console.log('time', new Date().getTime());
  next(action);
}
...
const time = timeMiddleware(store);
store.dispatch = exception(time(logger(next)));

本小节完整源码见demo-6 (opens new window)

# 中间件使用方式优化

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

import loggerMiddleware from './middlewares/loggerMiddleware';
import exceptionMiddleware from './middlewares/exceptionMiddleware';
import timeMiddleware from './middlewares/timeMiddleware';

...

const store = createStore(reducer);
const next = store.dispatch;

const logger = loggerMiddleware(store);
const exception = exceptionMiddleware(store);
const time = timeMiddleware(store);
store.dispatch = exception(time(logger(next)));

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

先来看看期望的用法

// 接受旧的 createStore,返回新的 createStore
const newCreateStore = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleawre)(createStore);

// 返回了一个 dispatch 被重写过的 store
const store = newCreateStore(reducer);

实现applyMiddleware

const applyMiddleware = function (...middlewares) {
  // 返回一个重写createStore的方法
  return function rewriteCreateStoreFunc(oldCreateStore) {
    // 返回重写后新的 createStore
    return function newCreateStore(reducer, initState) {
      // 1. 生成store
      const store = oldCreateStore(reducer, initState);
      // 给每个middleware传下store,相当于 const logger = loggerMiddleware(store);
      // const chain = [exception, time, looger];
      const chain = middlewares.map(middleware => middleware(store));
      let dispatch = store.dispatch;
      // 实现 exception(time(logger(dispatch)))
      chain.reverse().map(middleware => {
        dispatch = middleware(dispatch);
      });
      
      // 重写 dispatch
      store.dispatch = dispatch;
      return store;
    }
  }
}

# 让用户体验美好

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

// 没有中间件的 createStore
import { createStore } from './redux';
const store = createStore(reducer, initState);

// 有中间件的 createStore
const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);
const newCreateStore = rewriteCreateStoreFunc(createStore);
const store = newCreateStore(reducer, initState);

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

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

最终的用法

const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);

const store = createStore(reducer, initState, rewriteCreateStoreFunc);

本小节完整源码见demo-7 (opens new window)

最近更新时间: 2020/5/26 19:03:58