Redux中间件实现机制

2,815 阅读5分钟

中间件解决了什么问题?

Redux 引入中间件机制,其实就是为了在 dispatch 前后,统一“做爱做的事”。。。

摘自https://github.com/kenberkeley/redux-simple-tutorial/blob/master/redux-advanced-tutorial.md

例如我想在dispatch(action)发起前打印出日志,发起后再打印出日志以供调试,中间件就是干这事的。

中间件实现原理

中间件会在每次dispatch的时候执行!!!每次dispatch都会重新执行一次所有的中间件。由于引入了中间件机制,所以我们可以自己开发中间件,而redux只需要让我们写的中间件执行即可。下面就是"组装"第三方中间件成品的方法。

applyMiddleWare.js

代码33行:

for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key]; // 将传入的中间件存放到middlewares数组中
}  
var store = createStore(reducer, preloadedState, enhancer);
var _dispatch = store.dispatch;

applyMiddleWare方法作为createStore的第三个参数,其作用可以认为是用来增强dispatch方法的,所以当applyMiddleWare方法执行时,首先会先创建store对象,然后令_dispatch变量等于原始的dispatch方法。

试想:一个成品中间件需要哪些参数?

  1. store对象肯定是需要的,因为总得知道数据是啥才能打印出来的对吧。
  2. dispatch方法也是需要的,因为我们都任由中间件在做"他想做的事"了,中间件当然也不能忘了自己的本职工作: 将dispatch执行到底。

于是代码38行

var middlewareAPI = {
    getState: store.getState,
    dispatch: function dispatch(action) {
    return _dispatch(action);
}

创建了一个叫middlewareAPI的对象,提供给各个中间件使用。这个对象包含有store对象核心属性,可以将看做是store的简化版本,

下面我们将写个logger中间件以此来看中间件是怎么执行的:

logger中间件:作用就是在dispatch执行前后打印出store的值和action的type
const logger = (store) => (next) => (action) =>{
    console.group(action.type)
    console.info('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
      console.groupEnd(action.type)
     return result
}

中间件通过如下方式被组装:logger作为参数被传入applyMiddleware方法
let store = createStore(reducer,{data1:0},compose(
    applyMiddleware(logger),
))

下面我们将以logger中间件为例来看他在applyMiddleWare方法中经历了些什么。

代码45行

chain = middlewares.map(function (middleware) {   此处的middleware参数就是我们的logger中间件
    return middleware(middlewareAPI);
  });

可以看到这一步仅仅是将简化版store==>middlewareAPI对象传入了logger中。嘛,不错嘛,现在logger可以拿到store的数据了。

_dispatch = compose.apply(undefined, chain)(store.dispatch);

代码57行

_dispatch = compose.apply(undefined, chain)(store.dispatch);

全redux最牛叉的一行,就这一行,中间件的组装工作就完事了。完事了。。

compose函数的作用:将上一个函数的执行结果作为参数传递到下个一个函数中去,以此类推。可以认为后面的函数保存着上个函数的闭包。详细看这里

回到原点,中间件是用来增强dispatch的。所以上面compose初始传入参数为store.dispatch这个最最原始的dispatch方法,然后我们的logger中间件通过next参数接收到了store.dispatch,由于接收到了dispatch这个方法,所以logger这个function胆气很肥,认为老子相当牛啊,"劫持了"dispatch这个redux亲儿子方法,那我岂不是可以摇身一变顶替dispatch?so他这样做了。只见,他先是大喊了两声我很正常啊大家都把我就是真正的dispatch方法童叟无欺,然后在合适的时候执行了dispatch(),这样一来大家果然都被骗到了,他真的dispatch并且触发所有的监听函数了耶,他果然是真的。就这样我们的logger中间件狸猫换太子,取代了dispatch方法。但是有个潜在的风险还存在着,如果我们的logger中间件后面还有第二个、第三个中间件,那么logger(老哥)的太子地位又会被第二个中间件给篡夺,然后logger就成了给别的中间件打苦力的了。周而复始,尔虞我诈,黄雀在后,终于有一个中间件成功继承大统,没有人来篡位,他把上一个中间件收拾的服服帖帖,而上一个中间件又将上上个失败者收拾的服服帖帖......最后这个成功者被合到了store身上,成功继承了dispatch之名。由于applyMiddleWare只是增强了dispatch,所以其他的变量一概不动,原样继承,只有dispatch换成了新的。

return _extends({}, store, {
    dispatch: _dispatch   // 新 dispatch 覆盖原 dispatch,往后调用 dispatch 就会依次触发 chain 内的中间件链式串联执行,最后必定会执行最原始的dispatch(action)
  });

异步action的实现——14行代码就能的7千多颗星的redux-thunk中间件

异步action的使用场景:

异步操作至少要送出两个 Action:用户触发第一个 Action,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action 呢?

下面来看看redux-thunk干了什么

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
        // 如果redux-thunk中间件检测到action不是一个object,那么不会触发dispatch,而是将dispatch方法传到action方法里,并执行action,由action决定在何时触发dispatch
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

简单的说就是redux-thunk这个中间件讲他劫持到的dispatch方法吐给了用户,让用户决定何时触发,虽然这个dispatch方法就是用户本身触发的。。转手了一圈,又回到用户手里了。最后提一句,thunk中间件最好放在最后面,如果放在前面可能会导致后面的中间件得不到执行。

代码在这里 五行缺星

参考:

Redux 入门教程(二):中间件与异步操作 Redux中文文档 Redux 莞式教程。本教程深入浅出,配套入门、进阶源码解读以及文档注释丰满的 Demo 等一条龙服务