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处理过的中间件即可,核心代码如下:

js 复制代码
// 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)
)
js 复制代码
// 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>
    );
  }
}

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

redux中间件的基本使用demo

源码分析

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

js 复制代码
function xxx({getState, dispatch}) {
	// next就是聚合函数componse中的下一个要执行的函数(reducer中的func)
  return (next) => (action) => {
  	// todo...
  	// returnValue就是该中间件处理完之后返回给下一个中间件的值
    return returnValue;
  };
}

代码实现

redux-promise

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

js 复制代码
function promise({getState, dispatch}) {
  return (next) => (action) => {
  	// 判断传递进来的action是否为promise
    return isPromise(action) ? action.then(dispatch) : next(action);
  };
}

redux-thunk

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

js 复制代码
// 自定义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);
  };
}

redux-logger

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

其核心代码如下:

js 复制代码
// 自定义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;
  };
}

我们将示例中的中间件部分换成我们自己开发的中间件查看下效果:

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

相关资料

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

相关推荐
阿昌喜欢吃黄桃6 天前
RocketMq事务消息原理
java·中间件·消息队列·rocketmq·mq
半夜修仙7 天前
延迟队列的介绍及常见问题
java·数据库·中间件·rabbitmq
手握风云-7 天前
一条消息的旅程:RabbitMQ 学习与实践(一)
中间件·rabbitmq
RH2312118 天前
2026.6.8Linux
java·数据库·中间件
理人综艺好会9 天前
双Token机制在实际项目中的应用与实践
中间件·token
番茄去哪了9 天前
神领物流面试题(一)
java·大数据·中间件
念何架构之路9 天前
消息中间件
中间件
都说名字长不会被发现9 天前
Spring Boot Starter 中间件账号密码加密方案设计与实现
java·spring boot·后端·中间件
瀚高PG实验室10 天前
java中间件无法连接数据库
java·数据库·中间件·瀚高数据库
之歆10 天前
Day11_Express 深入解析:从中间件到项目实战
中间件·express