当前位置: 首页 > 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配置简述
  • 聊聊编译时注解
  • 微服务架构—高级设计篇
  • 漫谈版本控制系统
  • C学习-枚举(九)
  • download使用浅析
  • gcc介绍及安装
  • JavaScript函数式编程(一)
  • Laravel核心解读--Facades
  • Python连接Oracle
  • SwizzleMethod 黑魔法
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 关于for循环的简单归纳
  • 好的网址,关于.net 4.0 ,vs 2010
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 机器人定位导航技术 激光SLAM与视觉SLAM谁更胜一筹?
  • 赢得Docker挑战最佳实践
  • 原创:新手布局福音!微信小程序使用flex的一些基础样式属性(一)
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • kubernetes资源对象--ingress
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • #HarmonyOS:软件安装window和mac预览Hello World
  • (2)关于RabbitMq 的 Topic Exchange 主题交换机
  • (二十一)devops持续集成开发——使用jenkins的Docker Pipeline插件完成docker项目的pipeline流水线发布
  • (转)memcache、redis缓存
  • (转)Windows2003安全设置/维护
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • .a文件和.so文件
  • .htaccess 强制https 单独排除某个目录
  • .NET Framework杂记
  • .NET HttpWebRequest、WebClient、HttpClient
  • .NET 中创建支持集合初始化器的类型
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • @value 静态变量_Python彻底搞懂:变量、对象、赋值、引用、拷贝
  • [acwing周赛复盘] 第 69 场周赛20220917
  • [BSGS算法]纯水斐波那契数列
  • [BZOJ] 1001: [BeiJing2006]狼抓兔子
  • [BZOJ] 2044: 三维导弹拦截
  • [BZOJ3757] 苹果树
  • [C# 开发技巧]如何使不符合要求的元素等于离它最近的一个元素
  • [CC2642r1] ble5 stacks 蓝牙协议栈 介绍和理解
  • [DevOps云实践] 彻底删除AWS云资源