zoukankan      html  css  js  c++  java
  • redux middleware 的理解

    前言

    这几天看了redux middleware的运用与实现原理,写了一个百度搜索的demo,实现了类似redux-thunk和redux-logger中间件的功能。

    项目地址:https://github.com/CanFoo/react-baidu-search/tree/master

     

    redux中间件是通过函数式编程实现,因此要阅读源码需要有一定函数式编程基础,比如柯里化函数的实现,否则难以理解源码的缘由。接下去通过这个demo给大家讲解个人对中间件的理解,如有问题,come on 指正。

    redux middleware是什么

    如果没有中间件的运用,redux 的工作流程是这样 action -> reducer,这是相当于同步操作,由dispatch 触发action后,直接去reducer执行相应的动作。但是在某些比较复杂的业务逻辑中,这种同步的实现方式并不能很好的解决我们的问题。比如我们有一个这样的需求,点击按钮 -> 获取服务器数据 -> 渲染视图,因为获取服务器数据是需要异步实现,所以这时候我就需要引入中间件改变redux同步执行的流程,形成异步流程来实现我们所要的逻辑,有了中间件,redux 的工作流程就变成这样 action -> middlewares -> reducer,点击按钮就相当于dispatch 触发action,接下去获取服务器数据 middlewares 的执行,当 middlewares 成功获取到服务器就去触发reducer对应的动作,更新需要渲染视图的数据。中间件的机制可以让我们改变数据流,实现如异步 action ,action 过滤,日志输出,异常报告等功能。

    如何自定义中间件

    redux 提供了一个叫 applyMiddleware 的方法,可以应用多个中间件,这样就可以当触发action时候就会被这些中间件给捕获,这边我们分别定义了搜索和日志的中间件,而且中间件传入顺序是先搜索中间件再日志打印中间件,这个顺序是有讲究的,下文会说明的

    import { createStore } from 'redux'
    import applyMiddleware from './applyMiddleware/applyMiddleware'
    import compose from './applyMiddleware/compose'
    import reducer from './reducers'
    import loggerMiddleware from './middlewares/loggerMiddleware'
    import searchMiddleware from './middlewares/searchMiddleware'
    
    const createStoreWithMiddleware = compose(
        applyMiddleware(
            searchMiddleware,
            loggerMiddleware
        ),
        window.devToolsExtension ? window.devToolsExtension() : f => f
    )(createStore)

    searchMiddleware是搜索中间件,其实它就是仿照redux-thunk的实现

    export default function thunkMiddleware({ dispatch, getState }) {
      return next => action => {
        /*如果action是一个函数,则先执行action,否则通过next进入到下一个action对应的reducer*/
        typeof action === 'function' ?
          action(dispatch, getState) : //这里action(dispatch, getState)指的是getThenShow返回函数的执行
          next(action);
    }}

    我们先来分析下中间件的执行顺序,下文再来解释为什么这里结构是() => next => action => {}以及源码是如何实现中间件链。这里的代码非常简单,就是一个三目运算符,当action是一个函数类型,就直接执行这个action函数,否则就通过next来链接到下一个组件,next是专门用来串联组件间的执行顺序。我们知道,如果没有中间件的处理,action只能返回一个对象格式,否则reducer不能进行处理action传过来的行为,但是有了中间件,我们就可以“肆意妄为”,只要保证最终传给reducer的action是一个对象,期间从触发action到真正到达reducer我想要action是什么就可以是什么,下面是搜索逻辑所对应的action

    export function getThenShow(value) {
      return dispatch => {
        const url = 'https://gsp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su';
        jsonp(url, {wd: value}, 'cb', (data) => {
            dispatch({
              type: SHOW_MESSAGE_SUCESS,
              lists: data.s
            });
        })
      }
    }

    当执行dispatch(getThenShow())时,上文所说的searchMiddleware就会捕获到这个action,因为第一次传过来的action是

    functon(dispatch) {
        const url = 'https://gsp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su';
        jsonp(url, {wd: value}, 'cb', (data) => {
            dispatch({
              type: SHOW_MESSAGE_SUCESS,
              lists: data.s
        });
    })

    如上所示,因为第一次传过来的action是function类型的,所以searchMiddleware里就会先执行这个action函数,这个aciton执行jsonp方法回调另一个dispatch,从上面代码可以看出这个时候dispatch分发的action是一个对象,type为SHOW_MESSAGE_SUCCESS,lists为搜索结果,因此下一次searchMiddleware捕获到的action为一个对象,从而去执行next(action),也就是去执行下一个中间件loggerMiddleware的内容

    export default function createLogger({ getState }) {
        return (next) => (action) => {
            const prevState = getState();
            const returnValue = next(action);
            const nextState = getState();
            console.log(`%c prev state`, `color: #9E9E9E`, prevState);
            console.log(`%c action`, `color: #03A9F4`, action);
            console.log(`%c next state`, `color: #4CAF50`, nextState);
            console.log('===============')
            return returnValue;
        };
    }

    loggerMiddleware里先通过getState()获取到当前状态,接着用通过next(action)执行下一个中间件,由于applyMiddleware传入中间件的顺序是先searchMiddleware再loggerMiddleware,而且loggerMiddleware之后没有其它中间件传入,因此此时的next指的是原生的dispatch,进而会去触发reducer所对应的动作,所以再次调用getSatate()会返回下一个状态内容。

    所以当在搜索框输入内容redux执行的步骤为:

    1.输入内容触发dispatch(getThenShow())发起第一个action(这里getThenShow()返回的action的是一个函数)

    2.searchMiddleware捕获到第一个action,因为action返回是一个函数,所以执行action

    3.第一个action执行完后回调再次触发dispatch({type: SHOW_MESSAGE_SUCCESS, lists: ...})发起第二个action

    4.searchMiddleware捕获到第二个action,执行next(action),链接到loggerMiddleware

    5.loggerMiddleware获取当前(prev)状态后执行next(action)从而触发reducer的对应的动作

    6.loggerMiddleware再次获取当前(next)状态,然后打印出状态,执行完loggerMiddleware程序

    7.回溯到searchMiddleware,searchMiddleware程序执行完

    8.整个redux中间件执行完

    到这里,我们就把这个demo的中间件执行顺序分析完,那么为什么自定义中间件是() => next => action => {}这样的结构?next又是如何将中间件串联起来的?其实这个两个问题是同一个问题,因为中间件设定这样() => next => action => {}的结构目的就是为了把中间件串联起来的。为了一探究竟,我们还是得来来看看applyMiddleware源码的实现

    applyMiddleware 源码分析

    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的步骤放在这个闭包内执行,这样中间件就可以共享 store 对象。applyMiddleware是这样来对传进来中间件进行函数式编程处理的

    1.通过...middlewares将所有的中间件存入到middlewares数组中

    2.middlewares 数组通过 map 方法执行生成新的 middlewares 数组且每一项都传入middlewareAPI,传入middlewareAPI的目的就使得每个中间件都可以访问到store,这时候middlewares数组的每一项都变为了

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

    3.compose 方法将新的 middlewares 和 store.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)
      const fn = (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
      return fn
    }

    可以看到 compose 方法实际上就是利用Array.prototype.reduceRight来进行下一步处理的。如果对reduceRight方法不了解的童鞋,可以先看看这里。我们这里可以来模拟一下compose函数处理完的结果,假设我们这边有两个中间件A和B,则传入到compose的func为[A, B],且A、B的形式已经是(next) => (action) => {}

    function A(next) {
        console.log('A...next === ', next)
        return function(action) {
            console.log('A...action')
            next(action)
        }
    }
    function B(next) {
        console.log('B...next === ', next)
        return function(action) {
            console.log('B...action')
            next(action)
        }
    }
    
    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)
      const fn = (args) => rest.reduceRight((composed, f) => f(composed), last(args))
      return fn
    }
    
    var fnArr = [A, B]
    var dispatch = compose(fnArr)("store.dispatch")
    console.log('new dispatch === ', dispatch)

    执行的结果是

    由结果可以看到中间件A的next是指向中间件B的最内层闭包函数,而中间件B的next则是指向原生的dispatch,所以通过compose执行完后,所有的中间件就通过next串联起来了。这也就是为什么我们所分析这个百度搜索demo中的searchMiddleware的next是指向loggerMiddleware,而loggerMiddleware的next指向原生dispatch的原因。

    4.返回的 store 新增了一个 dispatch 方法, 这个新的 dispatch 方法是改装过的 dispatch,由上例中这个改装过的 dispatch就是指的是中间件A最里层的闭包函数,这也就是为什么说有了中间件就可以捕获action的行为的原理。

    到此applyMiddleware源码分析完毕,我们也可以明白为什么自定义组件需要设计成() => next => action => {}的形式,其实也就是设计成柯里化方式,因为这样方便进行compose,从而达到动态产生 next 方法以及保持 store 的一致性。


    教程源代码地址

    https://github.com/CanFoo/react-baidu-search/tree/master

  • 相关阅读:
    Python Revisited Day 13 (正则表达式)
    Python Revisited Day 06 (面向对象程序设计)
    Python Revisited (变量)
    Python Revisited Day 05(模块)
    Python Revisited Day 04 (控制结构与函数)
    Python Revisited Day 03 (组合数据类型)
    Numpy
    Python Revisited Day 01
    Python3使用openpyxl读写Excel文件
    Python3操作YAML文件
  • 原文地址:https://www.cnblogs.com/canfoo/p/6119446.html
Copyright © 2011-2022 走看看