6

Redux 中间件 到底怎么工作的呢?

 3 years ago
source link: https://my.oschina.net/jill1231/blog/5012880
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

中间件其实不仅仅是Redux专利,在Node框架中也有应用,比如koa,这里我们简单的默认为Redux 中间件,在进入正题前,先了解一下面向切面编程与中间件的关系

一、中间件与面向切面编程(AOP)

面向切面编程(AOP)的存在,解决了我们熟悉的面向对象(OOP)的局限性,可以将其看作是OOP的补充。比如当我们要为某几个类新增一段共同的逻辑,在OOP模式下,即可通过修改它们共同的父类来实现,但这无疑使得公共类越来越臃肿。那如果换成AOP,则可将 扩展逻辑在工作流中的执行节点视为一个单独“切点”,形成一个可以拦截前序逻辑的“切面”。

假设一个通用性很强,业务性很弱的日志追溯需求:要求在每个Action被派发后,打出一个console.log记录这个动作,面向切面编程(AOP)会如何处理?

可见,“切面”与业务逻辑是分离的,通过“即插即用”的方式自由的组织自己想要扩展的功能(异步工作流、性能打点、日志追溯等),它是典型的“非侵入式”的逻辑扩展思路,提升了组织逻辑的灵活度与干净度,规避逻辑冗余、逻辑耦合的问题。

二、中间件的引入

通过分析了Redux源码的主流程,我们可以肯定redux源码只有同步操作,也就是当dispatch action 时,state会被立即更新。若需要引入异步数据流,Redux官方则建议使用中间件来增强createStore的能力,它对外暴露了applyMiddleware函数,接受任意个中间件作为入参,返回作为createStore的入参的值

// 引入 redux
import { createStore } from 'redux'
// 创建 store
const store = createStore(
    reducer,
    initial_state,
    applyMiddleware(middleware1, middleware2, ...)
);

三、中间件的机制

  • 我们顺着中间件引入的角度,简单提取一下applyMiddleware源码框架,更加深刻的理解 applyMiddleware 是如何与 createStore 配合工作的?
// 使用“...”运算符将入参收敛为一个数组
function applyMiddleware(...middlewares) {

  // createStore 对应的是 createStore 函数本身,而 args 入参则对应的是 createStore 函数 约定的入参 reducer 和 preloadedState
  return createStore => (...args) => {
  
      // 调用 `createStore`,创建一个 `store`
      const store = createStore(...args)
      // 避免在接下来中间件的串联过程中,dispatch 被调用,即 不允许在构建中间件时进行调度
      let dispatch = () => {
          throw new Error(`Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.`)
      }
      
      ......下面代码依次放在此处......
      
  }
}
  • dispatch action 时,action 必须是一个普通对象,但使用过中间件的同学会发现 action 是允许为函数的,这背后applyMiddleware 是如何改写dispatch函数的?

1、以 middlewareAPI 作为中间件的入参,逐个调用传入的中间件,获取一个由“内层函数”组成的数组 chain

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

2、调用 compose 函数,将 chain 中的 “内层函数” 逐个组合起来,并调用最终组合出来的函数,传入 dispatch 作为入参

内层函数:在高阶函数中,习惯将原函数称为“外层函数”,将 return 出来的函数称为 “内层函数”

    dispatch = compose(...chain)(store.dispatch)

3、返回一个新的 store 对象,这个 store 对象的 dispatch 已经被改写过了

    return {
      ...store,
      dispatch
    }
  • 最后,我们深剖一下函数式编程中一个通用的概念,函数合成(compose 函数)
// 利用 ... 运算符将入参收敛为数组格式
function compose(...funcs) {
  // 处理数组为空的边界情况
  if (funcs.length === 0) {
    return arg => arg
  }
  // 若只有一个函数,也就谈不上组合,直接返回
  if (funcs.length === 1) {
    return funcs[0]
  }
  // 若有多个函数,那么调用 reduce 方法来实现函数的组合
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

reduce 会将数组中的每个元素执行指定的逻辑,并将结果汇总为单个返回值,假设有这样一个 compose 调用

compose(f1,f2,f3,f4)

函数会被组合成这样的形式

(...args) => f1(f2(f3(f4(...args))))

f1,f2,f3,f4这4个中间件的内层逻辑会被组合到一个函数中去,当这个函数被调用时,中间件会依次被调用

四、中间件的工作模式

从中间件的机制中,我们知道 任何的中间件都可以用自己的方式解析dispatch的内容,并继续传递actions 给下一个中间件。但注意:当最后一个中间件开始 dispatch action 时,action 必须是一个普通对象,因为这是同步式的 Redux 数据流 开始的地方。

1、redux-thunk源码解析

我们以为例,探究下中间件的工作模式 接下来,我们透过redux-thunk中间件的源码分析,验证上面的结论:

function createThunkMiddleware(extraArgument) {
  // 返回值是一个 thunk,它是一个函数
  return ({ dispatch, getState }) => (next) => (action) => {

    // thunk 若感知到 action 是一个函数,就会执行 action
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    // 若 action 不是一个函数,则不处理,直接放过
    return next(action);
  };
}

const thunk = createThunkMiddleware(); // 创建 thunk
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

redux-thunk源码层面可知道,它主要做的一件事就是 拦截到action后,检查它是否是一个函数

  • 若是函数,则执行它并返回执行的结果
  • 若不是函数,则直接调用next,工作流继续往下走

2、redux-thunk 模拟付款请求

现在,我们假设有这样一个需求:我们需要感知每一次付款请求的发送和响应,并处理请求的结果

import axios from 'axios' 
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

const store = createStore(
  reducer,
  applyMiddleware(thunk)
);
  • dispatch 一个 action,action 是一个函数
store.dispatch(payMoney(payInfo));
  • payMoney 的返回值是一个函数
// 支付信息
const payInfo = {
  userName: huamu,
  password: xxx,
  count: 1000000
}

const payMoney = (payInfo) => (dispatch) => {
  // 付款前发出准备信号
  dispatch({ type: 'payStart' })

  fetch().then(res => { dispatch()})

  return axios.post('/api/payMoney', {payInfo})
      .then(function (response) {
        console.log(response);
        // 付款成功信号
        dispatch({ type: 'paySuccess' })
      })
      .catch(function (error) {
        console.log(error);
        // 付款失败信号
        dispatch({ type: 'payError' })
      });
}

3、Redux的工作流

结合上面的分析,中间件的工作模式有如下两点可掌握

  • 中间件的执行时机:在action被分发之后、reducer触发之前
  • 中间件的执行前提:applyMiddleware函数对dispatch函数进行改写,使得dispatch触发reducer之前,执行Redux中间件的链式调用。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK