资讯专栏INFORMATION COLUMN

精益 React 学习指南 (Lean React)- 3.3 理解 redux 中间件

Kerr1Gan / 3283人阅读

摘要:数组为新的数组,包含了方法将新的和结合起来,生成一个新的方法返回的新增了一个方法,这个新的方法是改装过的,也就是封装了中间件的执行。

书籍完整目录

3.3 理解 Redux 中间件

这一小节会讲解 redux 中间件的原理,为下一节讲解 redux 异步 action 做铺垫,主要内容为:

Redux 中间件是什么

使用 Redux 中间件

logger 中间件结构分析

applyMiddleware

中间件的执行过程

3.3.1 Redux 中间件是什么

Redux moddleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.

redux 提供了类似后端 Express 的中间件概念,本质的目的是提供第三方插件的模式,自定义拦截
action -> reducer 的过程。变为 action -> middlewares -> reducer 。这种机制可以让我们改变数据流,实现如异步 action ,action 过滤,日志输出,异常报告等功能。

3.3.2 使用 Redux 中间件

Redux 提供了一个叫 applyMiddleware 的方法,可以应用多个中间件,以日志输出中间件为例

import { createStore, applyMiddleware } from "redux"
import createLogger from "redux-logger"
import rootReducer from "./reducers"

const loggerMiddleware = createLogger()
const initialState = {}

return createStore(
    rootReducer,
    initialState,
    applyMiddleware(
      loggerMiddleware
    )
  )
3.3.3 logger 中间件结构分析

看看 redux-logger 的源码结构

function createLogger(options = {}) {
  /**
   * 传入 applyMiddleWare 的函数
   * @param  {Function} { getState      }) [description]
   * @return {[type]}      [description]
   */
  return ({ getState }) => (next) => (action) => {
    let returnedValue;
    const logEntry = {};
    logEntry.prevState = stateTransformer(getState());
    logEntry.action = action;
    // .... 
    returnedValue = next(action);
    // ....
    logEntry.nextState = stateTransformer(getState());
    // ....
    return returnedValue;
  };
}

export default createLogger;

Logger 中这样的结构 ({ getState }) => (next) => (action) => {} 看起来是很奇怪的,这种设计如果没有 es6 的箭头函数,扩展下来就是

/**
 * getState 可以返回最新的应用 store 数据
 */
function ({getState}) {
    /**
     * next 表示执行后续的中间件,中间件有可能有多个
     */
    return function (next) {
        /**
         * 中间件处理函数,参数为当前执行的 action 
         */
        return function (action) {...}
    }
}

这样的结构本质上就是为了将 middleware 串联起来执行,为了分析 middleware 的执行顺序,还得看看 applyMiddleware 的实现

3.3.4 applyMiddleware 分析

下面是 applyMiddleware 完整的代码,参数为 middlewares 数组:

import compose from "./compose"

/**
 * Creates a store enhancer that applies middleware to the dispatch method
 * of the Redux store. This is handy for a variety of tasks, such as expressing
 * asynchronous actions in a concise manner, or logging every action payload.
 *
 * See `redux-thunk` package as an example of the Redux middleware.
 *
 * Because middleware is potentially asynchronous, this should be the first
 * store enhancer in the composition chain.
 *
 * Note that each middleware will be given the `dispatch` and `getState` functions
 * as named arguments.
 *
 * @param {...Function} middlewares The middleware chain to be applied.
 * @returns {Function} A store enhancer applying the middleware.
 */
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

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

    return {
      ...store,
      dispatch
    }
  }
}

applyMiddleware 执行过后返回一个闭包函数,目的是将创建 store的步骤放在这个闭包内执行,这样 middleware 就可以共享 store 对象。

middlewares 数组 map 为新的 middlewares 数组,包含了 middlewareAPI

compose 方法将新的 middlewaresstore.dispatch 结合起来,生成一个新的 dispatch 方法

返回的 store 新增了一个 dispatch 方法, 这个新的 dispatch 方法是改装过的 dispatch,也就是封装了中间件的执行。

所以关键点来到了 compose 方法了,下面来看一下 compose 的设计:

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

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

  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

可以看到 compose 方法实际上就是利用了 Array.prototype.reduceRight 。如果对 reduceRight 不是很熟悉,来看看下面的一个例子就清晰了:

/**
 * [description]
 * @param  {[type]} previousValue [前一个项]
 * @param  {[type]} currentValue  [当前项]
 */
[0, 1, 2, 3, 4].reduceRight(function(previousValue, currentValue, index, array) {
  return previousValue + currentValue;
}, 10);

执行结果:

# previousValue currentValue return value
第一次 10 4 14
第二次 14 3 17
第三次 17 2 19
第四次 19 1 20
第五次 20 0 20
3.3.5 理解中间件的执行过程

通过上面的 applyMiddleware 和 中间件的结构,假设应用了如下的中间件: [A, B, C],一个 action 的完整执行流程

初始化阶段

一个中间件的结构为

function ({getState}) {
    return function (next) {
        return function (action) {...}
    }
}

初始化阶段一:middlewares map 为新的 middlewares

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

执行过后,middleware 变为了

function (next) {
    return function (action) {...}
}

初始化阶段二:compose 新的 dispatch

const newDispatch = compose(newMiddlewares)(store.dispatch)

dispatch 的实现为 reduceRight, 当一个新的 action 来了过后

/**
 * 1. 初始值为: lastMiddleware(store.dispatch)
 * 2. previousValue: composed
 * 3. currentValue: currentMiddleware
 * 4. return value: currentMiddleware(composed) => newComposed
 */
rest.reduceRight((composed, f) => f(composed), last(...args))
composed 流程

reduceRight 的执行过程:

初始时候

initialValue: composedC = C(store.dispatch) = function C(action) {}

next 闭包: store.dispatch

第一次执行:

previousValue(composed): composedC

currentValue(f): B

return value: composedBC = B(composedC) = function B(action){}

next 闭包 composedC

第二次执行:

previousValue(composed): composedBC

currentValue(f): A

return value: composedABC = A(composedBC) = function A(action){}

next 闭包 composedBC

最后的返回结果为 composedABC

执行阶段

dispatch(action) 等于 composedABC(action) 等于执行 function A(action) {...}

在函数 A 中执行 next(action), 此时 A 中 nextcomposedBC,那么等于执行 composedBC(action) 等于执行 function B(action){...}

在函数 B 中执行 next(action), 此时 B 中 nextcomposedC,那么等于执行 composedC(action) 等于执行 function C(action){...}

在函数 C 中执行 next(action), 此时 C 中 nextstore.dispatch 即 store 原生的 dispatch, 等于执行 store.dispatch(action)

store.dispatch 会执行 reducer 生成最新的 store 数据

所有的 next 执行完过后开始回溯

执行函数 C 中 next 后的代码

执行函数 B 中 next 后的代码

执行函数 A 中 next 后的代码

整个执行 action 的过程为 A -> B -> C -> dispatch -> C -> B -> A

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

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

相关文章

  • 精益 React 学习指南Lean React)- 3.4 掌控 redux 异步

    摘要:举例来说一个异步的请求场景,可以如下实现任何异步的逻辑都可以,如等等也可以使用的和。实际上在中,一个就是一个函数。 书籍完整目录 3.4 redux 异步 showImg(https://segmentfault.com/img/bVyou8); 在大多数的前端业务场景中,需要和后端产生异步交互,在本节中,将详细讲解 redux 中的异步方案以及一些异步第三方组件,内容有: redu...

    JouyPub 评论0 收藏0
  • 精益 React 学习指南Lean React)- 3.5 compose redux saga

    摘要:通过可以实现很多有趣的简洁的控制。这里默认使用到了的一个特性,如果某一个任务成功了过后,其他任务都会被。组合是的内关键字,使用的场景是一个。 书籍完整目录 3.5 compose redux sages showImg(https://segmentfault.com/img/bVyoVa); 基于 redux-thunk 的实现特性,可以做到基于 promise 和递归的组合编排,而...

    Joyven 评论0 收藏0
  • 精益 React 学习指南Lean React)- 1.1 React 介绍

    摘要:单向数据流应用的核心设计模式,数据流向自顶向下我也是性子急的人,按照技术界的惯例,在学习一个技术前,首先得说一句。然而的单向数据流的设计让前端定位变得简单,页面的和数据的对应是唯一的我们可以通过定位数据变化就可以定位页面展现问题。 书籍完整目录 1.1 React 介绍 showImg(https://segmentfault.com/img/bVvJgS); 1.1.1 React ...

    lsxiao 评论0 收藏0
  • 精益 React 学习指南Lean React)- 4.2 react patterns

    摘要:另外一点是组件应该尽量保证独立性,避免和外部的耦合,使用全局事件造成了和外部事件的耦合。明确的职责分配也增加了应用的确定性明确只有组件能够知道状态数据,且是对应部分的数据。 书籍完整目录 4.2 react patterns 修改 Props Immutable data representation 确定性 在 getInitialState 中使用 props 私有状态和...

    Berwin 评论0 收藏0

发表评论

0条评论

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