当前位置: 首页 > news >正文

Redux 中间件分析

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

相关文章:

  • c# yield关键字原理详解
  • 一个日期处理类库moment.js
  • 使用Kolla构建Pike版本OpenStack Docker镜像
  • spring MVC 使用 hibernate validator验证框架,国际化配置
  • Kubernetes软件包管理系统-Helm架构
  • A - 夹角有多大(题目已修改,注意读题)
  • 卸载openssl后yum无法使用,ssh无法连接的解决办法
  • 春雪
  • (16)Reactor的测试——响应式Spring的道法术器
  • Oracle 谈 JavaFX 及 Java 客户端技术的未来
  • [译] React 中的受控组件和非受控组件
  • drbd配置简述
  • 聊聊编译时注解
  • 微服务架构—高级设计篇
  • 漫谈版本控制系统
  • ES6指北【2】—— 箭头函数
  • .pyc 想到的一些问题
  • “大数据应用场景”之隔壁老王(连载四)
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • conda常用的命令
  • Date型的使用
  • HTTP中GET与POST的区别 99%的错误认识
  • Java反射-动态类加载和重新加载
  • Java知识点总结(JavaIO-打印流)
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • linux安装openssl、swoole等扩展的具体步骤
  • maya建模与骨骼动画快速实现人工鱼
  • node入门
  • Python_OOP
  • webpack入门学习手记(二)
  • Webpack入门之遇到的那些坑,系列示例Demo
  • yii2权限控制rbac之rule详细讲解
  • 闭包,sync使用细节
  • 成为一名优秀的Developer的书单
  • 从零搭建Koa2 Server
  • 基于 Ueditor 的现代化编辑器 Neditor 1.5.4 发布
  • 今年的LC3大会没了?
  • 坑!为什么View.startAnimation不起作用?
  • 七牛云假注销小指南
  • 浅谈JavaScript的面向对象和它的封装、继承、多态
  • 容器服务kubernetes弹性伸缩高级用法
  • 微信小程序上拉加载:onReachBottom详解+设置触发距离
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 小程序开发之路(一)
  • 云大使推广中的常见热门问题
  • const的用法,特别是用在函数前面与后面的区别
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • ​2021半年盘点,不想你错过的重磅新书
  • ​LeetCode解法汇总1276. 不浪费原料的汉堡制作方案
  • ​卜东波研究员:高观点下的少儿计算思维
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • $NOIp2018$劝退记
  • %@ page import=%的用法
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (Java数据结构)ArrayList