• Redux中间件源码解析与实现


    基本介绍

    本文中涉及到的关键npm包的版本信息如下:
    react 的版本为18.2.0
    redux的版本为4.1.2
    redux-thunk版本为2.4.2
    redux-promise版本为0.6.0
    redux-logger版本为3.0.6

    Redux源码解析与实现(一)Redux源码解析与实现(二)这两篇文章中,详细讲解了怎么实现一个Redux的核心功能,而Redux默认只能够处理plainObject的参数,所以我们需要引用各种中间件来加强dispatch,使其能够传递异步函数或者是promise或者是其他的能力,比如说打日志。每个中间件往往也只做一件特定的事情,比如redux-thunk就是可以处理函数redux-logger可以打印出日志redux-promise可以处理actionpromise的情况。而且下一个中间件的接受的action为上一个中间件的处理的结果,如下图所示:

    请添加图片描述

    基本使用

    我们建一个简单的demo,看看redux-promise、redux-promise、redux-logger是如何使用的。我们只需要在createStore方法中传递经过applyMiddleware处理过的中间件即可,核心代码如下:

    // store/index.js
    import thunk from 'redux-thunk'
    import logger from 'redux-logger'
    import promise from 'redux-promise'
    
    const store = createStore(
      rootReducer,
      // logger 要在thunk的后面,dispatch接受的参数可能是函数经过thunk处理之后再return一个planObject再交给logger处理
      applyMiddleware(promise, thunk, logger)
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    
    // ReduxPage.jsx
    import React from "react";
    import store from "../store";
    import './index.css'
    
    export default class ReduxPage extends React.Component {
    
      componentDidMount() {
        // 新增订阅,在这里订阅的逻辑就是当状态管理库store中数据更新时,组件也需要更新一下
        this.unsubscribe = store.subscribe(() => {
          this.forceUpdate()
        })
      }
    
      componentWillUnmount() {
        // 组件卸载 -> 自然需要取消订阅
        this.unsubscribe()
      }
    
      add = () => {
        store.dispatch({type: "ADD"})
      }
    
      asyncAdd = () => {
        store.dispatch((dispatch, getState) => {
          console.log('getState pre:', getState());
          // 使用setTineout模拟后端请求
          setTimeout(() => {
            // 拿到服务端数据,处理完数据之后再dispatch
            dispatch({type: "ADD", payload: 10})
            console.log('getState after:', getState());
          }, 1000)
        })
      }
    
      promiseAdd = () => {
        // dispatch接受一个promise的参数
        // store.dispatch(Promise.resolve({type: 'ADD', payload: 20}))
        store.dispatch(new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve({type: 'ADD', payload: 20})
          }, 1000)
        }))
    
      }
    
      minus = () => {
        store.dispatch({type: "MINUS"})
      }
    
      setName = () => {
        store.dispatch({type: "SET_NAME", payload: `hyy${Math.random()}`})
      }
      
      setAge = () => {
        store.dispatch({type: "SET_AGE", payload: Math.random() * 100})
      }
    
      render() {
        return (
          <div>
            <div> ReduxPage </div>
            <div>
              <span className="box">count: {store.getState().count}</span>
              <span className="box">name: {store.getState().userInfo.name}</span>
              <span className="box">age: {store.getState().userInfo.age}</span>
            </div>
            <div className="wrap">
              <button onClick={this.add}>add</button>
              <button onClick={this.asyncAdd}>asyncAdd</button>
              <button onClick={this.promiseAdd}>promiseAdd</button>
              <button onClick={this.minus}>minus</button>
            </div>
            <div className="wrap">
              <button onClick={this.setName}>setName</button>
              <button onClick={this.setAge}>setAge</button>
            </div>
          </div>
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    经过Redux源码解析与实现(一)Redux源码解析与实现(二)我们知道在用户在dispatch的时候会依次执行各个中间件,而中间件会接受到dispatch & getStore这个对象以读写store中的数据然后返回值给下一个中间件执行。直至最后应该得到一个plainObject传递给原始的dispatch执行。其中通过applyMiddleware接受到的若干个中间件函数数组则是使用了函数式编程思想中函数聚合的方式依次执行。

    redux中间件的基本使用demo

    源码分析

    经过上面的分析,所有的中间件的代码架子均如下所示:

    function xxx({getState, dispatch}) {
    	// next就是聚合函数componse中的下一个要执行的函数(reducer中的func)
      return (next) => (action) => {
      	// todo...
      	// returnValue就是该中间件处理完之后返回给下一个中间件的值
        return returnValue;
      };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    代码实现

    redux-promise

    这个中间件主要是判断当前传递的参数即action是否是promise,如果是的话,那我们就需要通过promise的方式获取值然后返回给下一个中间件,如果不是的话,那就直接运行当前函数即可。其代码基本实现如下:

    function promise({getState, dispatch}) {
      return (next) => (action) => {
      	// 判断传递进来的action是否为promise
        return isPromise(action) ? action.then(dispatch) : next(action);
      };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    redux-thunk

    这个中间件主要是判断当前的action是否是函数,如果是函数的话,那就执行该函数并将dispatch & getStore两个操作状态管理库的API传递给这个函数,这个函数执行的结果return给下一个中间件的入参,如果不是一个函数,那就将action传递给当前函数执行&返回结果给下一个中间件其主要核心代码如下:

    // 自定义thunk中间件
    // 中间件接受的参数就是middlewareAPI即getState & dispatch 让中间件有操作状态管理库的权限
    function thunk({getState, dispatch}) {
      // ! next就是聚合函数componse中的下一个要执行的函数(reducer中的func)而不是下一个中间件
      return (next) => (action) => {
        if (typeof action === "function") {
          // 如果接受到一个函数的话就执行这个函数 & 把dispatch和getState作为参数传递给这个函数
          // 所以业务代码传递给dispatch的函数参数可以接受dispatch, getState这两个参数
          return action(dispatch, getState);
        }
        // 如果action不是函数那就正常执行 并把当前函数执行的值return给下一个中间件
        return next(action);
      };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    redux-logger

    这个中间件主要是在dispatch的时候打印出了一些日志,便于我们做数据追踪,我们可以在数据修改之前(即dispatch之前打印出原始数据 )+ action动作和数据被修改之后(即dispatch之后的store中的数据)
    其核心代码如下:

    // 自定义logger 中间件
    function logger({getState, dispatch}) {
      return (next) => (action) => {
        console.log("------------------------------------------");
        console.log("prev state", getState());
    
        console.log(`${action.type ? `ACTION: ${action.type + "已被执行~"}` : '接受到action为非plainObject'}`);
    
        const returnValue = next(action);
    
        console.log("next state", getState());
    
        console.log("------------------------------------------");
    
        return returnValue;
      };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    我们将示例中的中间件部分换成我们自己开发的中间件查看下效果:
    请添加图片描述

    线上demo: redux中间件的基本实现

    相关资料

    redux
    redux-thunk
    redux-logger
    redux-promise
    Redux源码解析与实现(一)
    Redux源码解析与实现(二)
    redux中间件的基本使用demo
    redux中间件的基本实现

  • 相关阅读:
    SpringCloud--nacos 入门使用
    二维数组转化为普通数组
    Mac(M1)安装mysqlclient失败解决办法-error: subprocess-exited-with-error
    C18-PEG- ALD批发_C18-PEG-CHO_C18-PEG-醛基
    关于HOperatorSet.CountChannels的注意事项
    LeetCode 18 四数之和
    ArcGIS Pro实践技术应用、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合
    有趣的手机软件分享,感兴趣的朋友来瞧瞧
    在ubuntu20下构建rtpengine
    传智健康产品需求说明书
  • 原文地址:https://blog.csdn.net/weixin_38080573/article/details/132717708