资讯专栏INFORMATION COLUMN

Redux 中间件分析

littlelightss / 2203人阅读

摘要:假设等于,其中,,是三个中间件,等于,那么可以简化为。最终返回中的方法以及经过中间件包装处理过的方法。以此类推,第二个返回的就是第一个中间件的形参。根据这个的讨论,在中间件顶层调用了,结果导致无法执行后面的中间件。

redux 主要包含 5 个方法,分别是:

createStore

combineReducers

bindActionCreators

applyMiddleware

compose

今天主要讲解下 applyMiddlewarecompose 这两个方法。在 redux 中引入了中间件的概念,没错如果你使用过 Express 或者 Koa 的话,一定不会对中间件陌生。我们知道,在 Koa 中,串联各个中间件的正是 compose 方法,所以在 redux 中也同样使用了这个命名,作用也是串联所有中间件。

reduce 用法

在正式讲解前,我们先来看下 reduce 的用法。根据 MDN 上的解释,

reduce() 方法是对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值。
arr.reduce(callback[, initialValue])
参数

callback

执行数组中每个值的函数,包含四个参数:

accumulator:累加器累加回调的返回值; 它是上一次调用回调时返回的累积值,或 initialValue

currentValue:数组中正在处理的元素。

currentIndex:数组中正在处理的当前元素的索引。 如果提供了initialValue,则索引号为0,否则为索引为1。

array:调用 reduce 的数组

initialValue

用作第一个调用 callback的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。

返回

函数累计处理的结果

compose 分析

有了上面 reduce 的基础,我们再来看下 compose 的代码。compose 的代码很简单,10行代码左右,但你看到 reduce 部分的时候,估计会一脸懵逼,短短的一行代码看上去却很绕。

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */
 
export default 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)))
}

看注释,它的作用应该是

执行 compose(f, g, h)
得到 (...args) => f(g(h(...args)))

我们来推导下,它是怎么得出这个结果的。假设 funcs 等于 [f1, f2, f3],其中 f1f2f3 是三个中间件,(a, b) => (..args) => a(b(...args)) 等于 f,那么 funcs.reduce((a, b) => (...args) => a(b(...args))) 可以简化为 [f1, f2, f3].reduce(f)

第 1 次执行 f

a = f1
b = f2 
返回 (...args) => f1(f2(..args))

第 2 次执行 f

a = (...args) => f1(f2(...args))
b = f3
返回 (...args) => a(f3(...args)) = f1(f2(f3(...args)))

通过上面的推导,证实了先前得出的结论

compise(f, g, h) = (...args) => f(g(h(...args)))
applyMiddleware 分析

通过上面的分析,我们知道 compose 是对中间件的串联,那么 applyMiddleware 就是对中间件的应用了。最终返回 createStore 中的方法以及经过中间件包装处理过的 dispatch 方法。

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.`
      )
    }
    let chain = []

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

    return {
      ...store,
      dispatch
    }
  }
}

我们通过一个具体的中间件 redux-thunk,来查看它内部到底是怎么来执行加载的中间件的。

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === "function") {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

中间件中包含了三个箭头函数,在 applyMiddleware 中的 map 操作后,返回了第二层箭头函数,所以 chain 中存储的是各个中间件的第二层函数。

根据 compose 的分析,

dispatch = compose(...chain)(store.dispatch)
等于
dispatch = f1(f2(f3(store.dispatch)))

我们先执行第三个中间件,并把返回结果作为第二个中间件的入参继续执行,以此类推,下一个中间件的入参是上一个中间件的返回。如果说这里第三个中间件是上面的 redux-thunk,那么函数中的 next 就是 store.dispatch,返回第三个箭头函数 action。这里返回的第三个箭头函数,就是第二个中间件的 next 形参。以此类推,第二个返回的 action 就是第一个中间件的 next 形参。但是这里都还没真正开始执行中间件。

当我们外部调用 store.dispatch(action) 方法的时候,才要真正开始执行各个中间件。首先执行中间件 f1,当执行到 next 的时候,开始执行第二个中间件 f2,以此类推直到最后一个中间件,调用原生 store.dispatch 方法。

之所以要写这么绕,也是为了符合 redux 单一数据源的原则,applyMiddleware 的写法保证了 action 的流向,而且每一步的数据变化都是可以追踪的。

其他

对比了 4.0.0-beta.1 之前版本的 applyMiddleware 的区别,发现内部 dispatch 从之前的 store.dispatch 改成了现在的直接抛出一个错误。根据这个 issues 的讨论,在中间件顶层调用了 store.dispatch,结果导致无法执行后面的中间件。这个调用应该是在处理 map 操作的时候执行的,此时的 applyMiddleware 还没执行完,store.dispatch 调用的还是原生 createStroe 中的方法才导致的这个问题。

另外如果在中间件中即 action 层使用 dispatch 会怎样呢?我们知道我们可以通过 next 进入到下个中间件,那如果调用 store.dispatch 的话又会从外层重新来一遍,假如这个中间件内部只是粗暴的调用 store.dispatch(action) 的话,就会形成死循环。如下图所示

参考
redux middleware 详解

Dispatching in a middleware before applyMiddleware completes

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93857.html

相关文章

  • Redux原理分析

    摘要:调用链中最后一个会接受真实的的方法作为参数,并借此结束调用链。总结我们常用的一般是除了和之外的方法,那个理解明白了,对于以后出现的问题会有很大帮助,本文只是针对最基础的进行解析,之后有机会继续解析对他的封装 前言 虽然一直使用redux+react-redux,但是并没有真正去讲redux最基础的部分理解透彻,我觉得理解明白redux会对react-redux有一个透彻的理解。 其实,...

    sumory 评论0 收藏0
  • redux 源码分析,实现一个迷你的redux

    摘要:实现一个先不考虑中间件,实现一个简洁的实现是最主要的一个了,通过可以创建一个用来存放应用中所有的,一个应用只能有一个。方法是用来把每一个用方法包裹一下,因为可能只是返回一个具有属性的对象,只有用执行才有意义。正好可以利用的特性实现这个效果。 实现一个redux 先不考虑中间件,实现一个简洁的redux 实现createStore createStore是redux最主要的一个API了,...

    Ashin 评论0 收藏0
  • React 项目中Redux 间件的理解

    摘要:如果想学习项目的底层建设,建议先去学习官网案例,之后在学习的使用中间件介绍目的是提供第三方插件的模式,改变的过程。 前言 React/Redux项目结束后,当我在研究react-router源码的时候发现当中有一部分含中间件的思想,所以才想把中间件重新梳理一遍;在之前看redux了解到中间件,redux层面中间件的理解对项目前期比较有帮助,虽然项目中后期基本可以忽略这层概念;现在对这部...

    amc 评论0 收藏0
  • redux浅析

    摘要:概念是一个状态管理容器使用可以更好的管理和监测组件之间需要通信的数据。参考源码参考链接 redux概念 redux是一个状态管理容器,使用redux可以更好的管理和监测组件之间需要通信的数据。 redux基本原则 单一数据源 在redux中,整个应用保持一个数据源,数据源是一个树形的结构 状态只读 状态只读意思是不能直接修改,需要通过dispatch action方式才可以,返回的是一...

    galois 评论0 收藏0
  • 精益 React 学习指南 (Lean React)- 3.3 理解 redux 间件

    摘要:数组为新的数组,包含了方法将新的和结合起来,生成一个新的方法返回的新增了一个方法,这个新的方法是改装过的,也就是封装了中间件的执行。 书籍完整目录 3.3 理解 Redux 中间件 showImg(https://segmentfault.com/img/bVymkt); 这一小节会讲解 redux 中间件的原理,为下一节讲解 redux 异步 action 做铺垫,主要内容为: ...

    Kerr1Gan 评论0 收藏0

发表评论

0条评论

littlelightss

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<