Redux 学习之 middleware详解

什么是middleware

middleware翻译过来叫中间件,这是一个很抽象的名词,按个人理解的话:“中间”是个形容词,“件”应该是一个名词。那么咱们重点关注中间这个词,中间,是在上面中间呢?其实就是在你执行正常业务的代码中间插入一部分其它代码,具体能够是在正常代码的执行前,也能够在正常代码执行后。其实学过Spring的童鞋应该很好理解,这个东西跟Spring的切面编程很相似。。。编程

有啥用

前面说了,这个技术可让咱们在正常的业务代码先后执行一部分其它代码,这个其它代码能够包括:日志、鉴权啊等到一些公共处理代码。简单来讲,只有你想不到,没有作不到。redux

怎么用

话很少说,先上代码:数组

const logger1 =store=> next=> action => {
  console.log('dispatching  logger', action)
  next(action)
  console.log('next state  logger', action)
}

const logger2 =store=> next=> action => {
  console.log('dispatching  logger', action)
  next(action)
  console.log('next state  logger', action)
}

const store = createStore(
  reducer,
  applyMiddleware([logger1,logger2])
)

咱们看到上面的代码中,首先声明logger一、logger2两个middleware(没错,这两个看起来很奇怪的变量就是middleware了。。。),而后在建立store的时候经过applyMiddleware来绑定到dispatch上去,这样每次咱们分发(dispatch)一个action的时候,两个middleware里的代码都会执行。闭包

对,就是这么简单,表面的简单,背后是大量逻辑很复杂的代码。。。app

源码解析

下面先上applyMiddleware的源码:函数

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

这个函数是redux在createStore函数中调用的,因此它返回一个匿名函数,咱们只须要关心内部匿名函数的实现就行了。优化

让咱们来一步步分析短短的几行代码:this

变量声明

咱们先看applyMiddleware内部匿名函数的前几行代码:spa

const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }

第一行代码调用createStore函数建立一个store对象,这个没什么特别的,过。。。
第二行声明一个dispatch变量,指向一个箭头函数,函数直接报错,用来告诉用户我这会儿正初始化呢,你敢dispatch我就报错给你看!
第三行代码声明一个middlewareAPI的对象,里面包含两个属性:getState和dispatch。翻译

getState没什么好说的,重点是这个dispatch属性,这个属性指向一个箭头函数,函数直接执行dispatch函数(这个dispatch函数可不是store原生的dispatch,而是咱们在第二行声明的dispatch变量指向的箭头函数。

这块相对来讲比较简单,可是为了后面咱们好理解,这里来对前面的变量声明作以下优化:

const store = createStore(...args) // 不变
    let temDispatch = () => {  // 为了跟store.dispatch 区分,咱们把变量名称修改成temDispatch;
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      APIDispatch: (...args) => temDispatch(...args) // 一样的,为了区分,咱们这里用APIDispatch来表示属性变量
    }

如上代码所示,为了同store.dispatch方法区分,咱们分别用 temDispatch和 APIDispatch这两个名称来替代原来的dispatch。

分拆Middleware 函数

接下来咱们看下一行代码:

const chain = middlewares.map(middleware => middleware(middlewareAPI))

middlewares咱们知道是一个包含中间件的数组,经过数组的map处理后,咱们将会"执行一次"中间件函数,而后将返回值放到chain的数组中。

上面咱们说"执行一次“中间件函数,其实说法有点不太好理解,接下来咱们慢慢分析中间件:

const logger1 =store=> next=> action => {
  console.log('dispatching  logger', action)
  next(action)
  console.log('next state  logger', action)
}

上面是一个最简单的中间件形式,可是仍是有点复杂,咱们能够先把这个中间件拆分红如下的样子:

const inner = action => {
  console.log('dispatching 333 logger', action)
  next(action)
  console.log('next state 4444 logger', action)
};

const middle = next => action => {
  console.log('dispatching 333 logger', action)
  next(action)
  console.log('next state 4444 logger', action)
};

const logger1 = store => next => action => {
  console.log('dispatching  logger', action)
  next(action)
  console.log('next state  logger', action)
}

如上所示,咱们的middleware实际上是一个箭头函数,不严谨的说,这个函数能够被logger1()()()这样被调用,由于第一次和第二次被调用都返回一个新的箭头函数,这里为了好理解,咱们把他们拆分为middle 和inner函数(通常是不能这么写的,由于内部的箭头函数还要经过闭包获取外部的变量值)。

说了这么多,其实最终能够归结为一句话,那就是咱们的chain数组里放的都是middle函数,也就是chain是一个middle函数的集合,这点很重要,咱们后面还会说到这个。

链式调用

咱们继续看下一行代码:

dispatch = compose(...chain)(store.dispatch)
等价于
temDispatch = compose(...chain)(store.dispatch)

这行代码看着很简短,其实很难理解,咱们一步步来看。

转换链式函数

咱们首先来看 compose(...chain)这行代码。如下是compose代码的实现:

function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

关键代码funcs.reduce((a, b) => (...args) => a(b(...args))), 代入compose(...chain),chain咱们上面说到,是一个middle函数的数组,而后通过reduce处理,这里比较麻烦,咱们一点点来解释:

(a, b) => (...args) => a(b(...args))

上面就是reduce函数的回调,

(...args) => a(b(...args) 这是回调的返回值,也是一个箭头函数,咱们把它命名为reduceMiddleFunc;

a变量为上次回调的返回值(不出意外的话,就是一个箭头函数,要么是chain数组的第一个值,也就是一个middle函数,要么就是上次回调的返回值,就是一个reduceMiddleFunc函数 ),

b变量为当前循环的值,也就是一个middle函数。

这样可能不太好理解,举个例子吧,假如说原来的chain数组的值为[middle1,middle2,middle3,middle4]。那么compose(...chain)以后,咱们获得(...args) => middle1(middle2(middle3(middle4(...args))))这样一个箭头函数。咱们把它命名为 chainFunc.

执行链式函数

原来的代码是:

dispatch = compose(...chain)(store.dispatch)
等价于
temDispatch = compose(...chain)(store.dispatch)

通过咱们上面的分析后,咱们获得如下代码:

const chainFunc = (...args) => middle1(middle2(middle3(middle4(...args))));
temDispatch = chainFunc(store.dispatch)

接下来咱们来看 chainFunc(store.dispatch),也就是咱们要执行这个链式函数了,以下:

const chainFunc = (...args) => middle1(middle2(middle3(middle4(...args))));
temDispatch = chainFunc(store.dispatch) 
// 至关于下面一行
temDispatch = middle1(middle2(middle3(middle4(store.dispatch))))

【注意】:此处的store.dispatch是调用createStore建立的元素store的 dispatch方法,后面咱们会覆盖原生的dispatch,因此这里须要注意下。

返回store对象

咱们来看applyMiddleware的最后一行代码,

return {
      ...store,
      dispatch// 也就是temDispatch
    }

这个实际上是createStore函数的返回值,也就是说咱们上面定义的temDispatch会覆盖掉初始的store中dispatch。

也就是说,当你调用调用store.dispatch(action)的时候,就至关因而调用 middle1(middle2(middle3(middle4(store.dispatch))))(action),只要最内部的store.dispatch才是调用真正的dispatch方法。

咱们来简化一下这个代码:

const param = middle2(middle3(middle4(store.dispatch)));
store.dispatch(action)
等价于
middle1(param)(action)

还记得middle函数吗?

const middle = next => action => {
  console.log('dispatching 333 logger', action)
  next(action)
  console.log('next state 4444 logger', action)
};

当咱们执行middle1的时候,就会把param当作next参数来执行,而后返回一个 inner函数:

这是inner函数:

const inner = action => {
  console.log('dispatching 333 logger', action)
  next(action)
  console.log('next state 4444 logger', action)
};

那么上面的代码就能够修改成以下:

const param = middle2(middle3(middle4(store.dispatch)));
store.dispatch(action)
等价于
middle1(param)(action)
等价于
inner(action)

那么在inner函数内执行next函数,其实就是执行middle2(middle3(middle4(store.dispatch)))这一套,依次类推,就比如是洋葱同样,一直执行到最内部真正的 store.dispatch方法为止。

one more thing

上面我说到,在最后咱们用temDispatch这个函数覆盖了原始的 store.dispatch函数,那若是咱们是inner中经过 store.dispatch去调用会发什么状况呢?

咱们已经说过,applyMiddleware最终会覆盖原始store上的dispatch方法,改为咱们的链式调用函数,若是在inner里调用store.dispatch,其实就至关于从新从链式函数的最外层的开始调用,这就进死循环了。。。

调用洋葱图

相关文章
相关标签/搜索