【React系列】Redux(二)中间件

news2024/10/5 15:32:01

本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg5MDAzNzkwNA==&action=getalbum&album_id=1566025152667107329)

一. 中间件的使用

1.1. 组件中异步请求

在之前简单的案例中,redux中保存的counter是一个本地定义的数据,我们可以直接通过同步的操作来dispatch actionstate就会被立即更新。

但是真实开发中,redux中保存的很多数据可能来自服务器,我们需要进行异步的请求,再将数据保存到redux中。

在之前学习网络请求的时候我们讲过,网络请求可以在class组件的componentDidMount中发送,所以我们可以有这样的结构:

在这里插入图片描述

我现在完成如下案例操作:

  • Home组件中请求bannersrecommends的数据;
  • Profile组件中展示bannersrecommends的数据;

redux代码进行如下修改:

reducer.js中添加state初始化数据和reducer函数中处理代码:

const initialState = {
  counter: 0,
  banners: [],
  recommends: []
}

function reducer(state = initialState, action) {
  switch (action.type) {
    case ADD_NUMBER:
      return { ...state, counter: state.counter + action.num };
    case SUB_NUMBER:
      return { ...state, counter: state.counter - action.num };
    case CHANGE_BANNER:
      return { ...state, banners: action.banners };
    case CHANGE_RECOMMEND:
      return { ...state, recommends: action.recommends };
    default:
      return state;
  }
}

constants中增加常量:

const CHANGE_BANNER = "CHANGE_BANNER";
const CHANGE_RECOMMEND = "CHANGE_RECOMMEND";

actionCreators.js中添加actions

const changeBannersAction = (banners) => ({
  type: CHANGE_BANNER,
  banners
}) 

const changeRecommendsAction = (recommends) => ({
  type: CHANGE_RECOMMEND,
  recommends
})

组件中代码代码修改:

Home组件:

import React, { PureComponent } from 'react';
import { connect } from "react-redux";

import axios from 'axios';

import {
  addAction,
  changeBannersAction,
  changeRecommendsAction
} from '../store/actionCreators';

class Home extends PureComponent {
  componentDidMount() {
    axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
      const data = res.data.data;
      this.props.changeBanners(data.banner.list);
      this.props.changeRecommends(data.recommend.list);
    })
  }

  ...其他业务代码
}

const mapStateToProps = state => {
  return {
    counter: state.counter
  }
}

const mapDispatchToProps = dispatch => {
  return {
    addNumber: function(number) {
      dispatch(addAction(number));
    },
    changeBanners(banners) {
      dispatch(changeBannersAction(banners));
    },
    changeRecommends(recommends) {
      dispatch(changeRecommendsAction(recommends));
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Home);

Profile组件:

import React, { PureComponent } from 'react';
import { connect } from "react-redux";

import {
  subAction
} from '../store/actionCreators';

class Profile extends PureComponent {
  render() {
    return (
      <div>
        Profile
        <div>
          <h2>当前计数: {this.props.counter}</h2>
          <button onClick={e => this.decrement()}>-1</button>
          <button onClick={e => this.subCounter()}>-5</button>
        </div>
        <h1>Banners</h1>
        <ul>
          {
            this.props.banners.map((item, index) => {
              return <li key={item.acm}>{item.title}</li>
            })
          }
        </ul>
        <h1>Recommends</h1>
        <ul>
          {
            this.props.recommends.map((item, index) => {
              return <li key={item.acm}>{item.title}</li>
            })
          }
        </ul>
      </div>
    )
  }

  ...其他逻辑代码
}

const mapStateToProps = state => {
  return {
    counter: state.counter,
    banners: state.banners,
    recommends: state.recommends
  }
}

const mapDispatchToProps = dispatch => {
  return {
    subNumber: function (number) {
      dispatch(subAction(number));
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Profile);

1.2. redux中异步请求

上面的代码有一个缺陷:

  • 我们必须将网络请求的异步代码放到组件的生命周期中来完成;
  • 事实上,网络请求到的数据也属于我们状态管理的一部分,更好的一种方式应该是将其也交给redux来管理;

在这里插入图片描述

但是在redux中如何可以进行异步的操作呢?

  • 答案就是使用中间件(Middleware)
  • 学习过Express或Koa框架的童鞋对中间件的概念一定不陌生;
  • 在这类框架中,Middleware可以帮助我们在请求和响应之间嵌入一些操作的代码,比如cookie解析、日志记录、文件压缩等操作;

redux也引入了中间件(Middleware)的概念:

  • 这个中间件的目的是在dispatchaction和最终达到的reducer之间,扩展一些自己的代码;
  • 比如日志记录、调用异步接口、添加代码调试功能等等;

我们现在要做的事情就是发送异步的网络请求,所以我们可以添加对应的中间件:

  • 这里官网推荐的、包括演示的网络请求的中间件是使用 redux-thunk

redux-thunk是如何做到让我们可以发送异步的请求呢?

  • 我们知道,默认情况下的dispatch(action)action需要是一个JavaScript的对象;
  • redux-thunk可以让dispatch(action函数)action可以是一个函数
  • 该函数会被调用,并且会传给这个函数一个dispatch函数和getState函数;
    • dispatch函数用于我们之后再次派发action
    • getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态;

如何使用 redux-thunk 呢?

  1. 安装 redux-thunk
yarn add redux-thunk
  1. 在创建store时传入应用了middlewareenhance函数
  • 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
  • enhancer作为第二个参数传入到createStore中;
// 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
const enhancer = applyMiddleware(thunkMiddleware);
// 将enhancer作为第二个参数传入到createStore中
const store = createStore(reducer, enhancer);
  1. 定义返回一个函数的action
const getHomeMultidataAction = () => {
  return (dispatch) => {
    axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
      const data = res.data.data;
      dispatch(changeBannersAction(data.banner.list));
      dispatch(changeRecommendsAction(data.recommend.list));
    })
  }
}
  • 注意:这里不是返回一个对象了,而是一个函数;
  • 该函数在dispatch之后会被执行;
  1. 修改home.js中的代码:
import React, { PureComponent } from 'react';
import { connect } from "react-redux";

import {
  addAction,
  getHomeMultidataAction
} from '../store/actionCreators';

class Home extends PureComponent {
  componentDidMount() {
    this.props.getHomeMultidata();
  }

  ...其他逻辑代码
}

...mapStatetoProps

const mapDispatchToProps = dispatch => {
  return {
    addNumber: function(number) {
      dispatch(addAction(number));
    },
    getHomeMultidata() {
      dispatch(getHomeMultidataAction());
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Home);

1.3. redux-devtools

我们之前讲过,redux可以方便的让我们对状态进行跟踪和调试,那么如何做到呢?

  • redux官网为我们提供了redux-devtools的工具;
  • 利用这个工具,我们可以知道每次状态是如何被修改的,修改前后的状态变化等等;

安装该工具需要两步:

  • 第一步:在对应的浏览器中安装相关的插件(比如Chrome浏览器扩展商店中搜索Redux DevTools即可,其他方法可以参考GitHub);
  • 第二步:在redux中集成devtools
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import reducer from './reducer.js';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

// 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
const enhancer = composeEnhancers(applyMiddleware(thunkMiddleware));
// 将enhancer作为第二个参数传入到createStore中
const store = createStore(reducer, enhancer);

export default store;

trace打开:

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) || compose;

在这里插入图片描述

1.4. redux-saga

1.4.1. ES6的generator

saga中间件使用了ES6的generator语法,所以我们有必须简单讲解一下:

  • 注意:我这里并没有列出generator的所有用法,事实上它的用法非常的灵活,大家可以自行去学习一下。

在JavaScript中编写一个普通的函数,进行调用会立即拿到这个函数的返回结果:

function foo() {
  return "Hello World";
}

foo() // Hello World

如果我们将这个函数编写成一个生成器函数:

function *foo() {
  yield "Hello";
  yield "World";
}

const iterator = foo();
console.log(iterator, typeof iterator); // 一个object类型的iterator对象

调用iteratornext函数,会销毁一次迭代器,并且返回一个yield的结果:

// 调用一次next()是消耗一次迭代器
iterator.next(); // {value: "Hello", done: false}
iterator.next(); // {value: "World", done: false}
iterator.next(); // {value: undefined, done: true}

研究一下foo生成器函数代码的执行顺序:

function *foo() {
  console.log("111111");
  yield "Hello";
  console.log("222222");
  yield "World";
  console.log("333333");
}

// 调用一次next()是消耗一次迭代器
iterator.next(); // {value: "Hello", done: false}
// 打印111111
iterator.next(); // {value: "World", done: false}
// 打印222222
iterator.next(); // {value: undefined, done: true}
// 打印333333

generatorpromise一起使用:

function *bar() {
  const result = yield new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Hello Generator");
      return "Hello";
    }, 2000);
  });
  console.log(result);
}

const bIterator = bar();
bIterator.next().value.then(res => {
  bIterator.next(res);
});

1.4.2. redux-saga的使用

  1. 装 redux-saga
yarn add redux-saga
  1. 集成 redux-saga 中间件
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducer.js';
import mySaga from './saga';

// 通过createSagaMiddleware函数来创建saga中间件
const sagaMiddleware = createSagaMiddleware();

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) || compose;

// 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
const enhancer = composeEnhancers(applyMiddleware(thunkMiddleware, sagaMiddleware));
// 将enhancer作为第二个参数传入到createStore中
const store = createStore(reducer, enhancer);

// 必须启动saga中间件,并且传入其要监听的generator
sagaMiddleware.run(mySaga);

export default store;
  1. saga.js 文件的编写:
import { takeEvery, put, all } from 'redux-saga/effects';
import axios from 'axios';

import {
  FETCH_HOME_MULTIDATA
} from "./constants";
import {
  changeBannersAction,
  changeRecommendsAction,
} from './actionCreators';

function* fetchHomeMultidata(action) {
  const res = yield axios.get("http://123.207.32.32:8000/home/multidata");
  console.log(res);
  const data = res.data.data;
  yield all([
    put(changeBannersAction(data.banner.list)),
    put(changeRecommendsAction(data.recommend.list))
  ])
}

function* mySaga() {
  yield takeEvery(FETCH_HOME_MULTIDATA, fetchHomeMultidata)
}

export default mySaga;
  • takeEvery:可以传入多个监听的actionType,每一个都可以被执行(对应有一个takeLastest,会取消前面的)
  • put:在saga中派发action不再是通过dispatch,而是通过put
  • all:可以在yield的时候put多个action

二. 中间件的原理

2.1. 打印日志需求

前面我们已经提过,中间件的目的是在redux中插入一些自己的操作:

  • 比如我们现在有一个需求,在dispatch之前,打印一下本次的action对象,dispatch完成之后可以打印一下最新的store state
  • 也就是我们需要将对应的代码插入到redux的某部分,让之后所有的dispatch都可以包含这样的操作;

如果没有中间件,我们是否可以实现类似的代码呢?

当然可以,类似下面的方式即可:

console.log("dispatching:", addAction(5));
store.dispatch(addAction(5));
console.log("new state:", store.getState());

console.log("dispatching:", addAction(10));
store.dispatch(subAction(10));
console.log("new state:", store.getState());

在这里插入图片描述

但是这种方式缺陷非常明显:

  • 首先,每一次的dispatch操作,我们都需要在前面加上这样的逻辑代码;
  • 其次,存在大量重复的代码,会非常麻烦和臃肿;

是否有一种更优雅的方式来处理这样的相同逻辑呢?

  • 我们可以将代码封装到一个独立的函数中
function dispatchAndLog(action) {
  console.log("dispatching:", action);
  store.dispatch(addAction(5));
  console.log("新的state:", store.getState());
}

dispatchAndLog(addAction(10));

但是这样的代码有一个非常大的缺陷:

  • 调用者(使用者)在使用我的dispatch时,必须使用我另外封装的一个函数dispatchAndLog
  • 显然,对于调用者来说,很难记住这样的API,更加习惯的方式是直接调用dispatch

我们来进一步对代码进行优化;

2.2. 修改dispatch

事实上,我们可以利用一个hack一点的技术:Monkey Patching,利用它可以修改原有的程序逻辑;

我们对代码进行如下的修改:

let next = store.dispatch;

function dispatchAndLog(action) {
  console.log("dispatching:", addAction(10));
  next(addAction(5));
  console.log("新的state:", store.getState());
}

store.dispatch = dispatchAndLog;
  • 这样就意味着我们已经直接修改了dispatch的调用过程;
  • 在调用dispatch的过程中,真正调用的函数其实是dispatchAndLog

在这里插入图片描述

当然,我们可以将它封装到一个模块中,只要调用这个模块中的函数,就可以对store进行这样的处理:

function patchLogging(store) {
  let next = store.dispatch;

  function dispatchAndLog(action) {
    console.log("dispatching:", action);
    next(addAction(5));
    console.log("新的state:", store.getState());
  }

  store.dispatch = dispatchAndLog;
}

2.3. thunk需求

redux-thunk的作用:

  • 我们知道redux中利用一个中间件redux-thunk可以让我们的dispatch不再只是处理对象,并且可以处理函数;
  • 那么redux-thunk中的基本实现过程是怎么样的呢?事实上非常的简单。

我们来看下面的代码:

function patchThunk(store) {
  let next = store.dispatch;

  function dispatchAndThunk(action) {
    if (typeof action === "function") {
      action(store.dispatch, store.getState);
    } else {
      next(action);
    }
  }

  store.dispatch = dispatchAndThunk;
}
  • 我们又对dispatch进行转换,这个dispatch会判断传入的

将两个patch应用起来,进行测试:

patchLogging(store);
patchThunk(store);

store.dispatch(addAction(10));

function getData(dispatch) {
  setTimeout(() => {
    dispatch(subAction(10));
  }, 1000)
}

// 传入函数
store.dispatch(getData);

2.4. 合并中间件

单个调用某个函数来合并中间件并不是特别的方便,我们可以封装一个函数来实现所有的中间件合并:

function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice();

  middlewares.forEach(middleware => {
    store.dispatch = middleware(store);
  })
}

applyMiddleware(store, [patchLogging, patchThunk]);

我们来理解一下上面操作之后,代码的流程:

在这里插入图片描述

当然,真实的中间件实现起来会更加的灵活,这里我们仅仅做一个抛砖引玉,有兴趣可以参考redux合并中间件的源码流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1357654.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker (portainer 安装nginx)

汉化版步骤可以参考&#xff1a;写文章-CSDN创作中心https://mp.csdn.net/mp_blog/creation/editor/135258056 一、创建容器 二、配置端口&#xff0c;以及容器卷挂载 挂载目录配置&#xff1a;(下方截图的目录如下&#xff0c;docker 改为 mydocker&#xff0c;用docker作为根…

pytest安装失败,报错Could not find a version that satisfies the requirement pytest

问题 安装pytest失败&#xff0c;尝试使用的命令有 pip install pytest pip3 install pytest pip install -U pytest pip install pytest -i https://pypi.tuna.tsinghua.edu.cn/simple但是都会报同样的错&#xff1a; 解决方案 发现可能是挂了梯子的原因&#xff0c;关掉…

Git保姆级安装教程

Git保姆级安装教程 一、去哪下载二、安装2.1 具体安装步骤2.2 设置全局用户签名 一、去哪下载 1、官网&#xff08;有最新版本&#xff09;&#xff1a;https://git-for-windows.github.io/ 2、本人学习时安装的版本&#xff0c;链接&#xff1a;https://pan.baidu.com/s/1uAo…

【完整流程】实现STM32+ESP8266+MQTT+阿里云+APP——【第二节-编写STM32程序初步实现ESP8266上云发布订阅消息】

&#x1f31f;博主领域&#xff1a;嵌入式领域&人工智能&软件开发 前言&#xff1a;本节实现&#xff0c;硬件连接STM32与ESP8266&#xff0c;编写STM32程序通过at命令方式实现STM32ESP8266与阿里云物联网平台发布订阅消息&#xff0c;本节最终实现初步的发布订阅消息…

asp.net core使用gb2312编码

nuget包 安装System.Text.Encoding.CodePages 使用 //将byte[]转化为gb2312的字符串&#xff0c;要确保byte[]是存储的gb2312的字符串&#xff0c;要不然会乱码 string ToGb213(byte[] str) {//首先需要注册Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)…

Tableau 各版本安装指南

Tableau下载链接 https://pan.baidu.com/s/1h43aEuSxxkOOa_JPwW_CDg?pwd0531 1.鼠标右击【Tableau2023(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到 Tableau2023(64bit)】。 2.打开解压后的文件夹&#xff0c;鼠标右击【Setup】…

vue-动态高亮效果

个人练习&#xff0c;仅供参考 实现如下效果&#xff1a;点击某块&#xff0c;某块变成其他颜色。 具体实现代码&#xff1a; 上边&#xff1a; <template><div><h3 style"color: #f69">动态高亮效果</h3><hr> <!-- 对象 -->…

PythonStudio=vb7国人写的python可视化窗体设计器IDE,可以替代pyqt designer等设计器了

【免费】PythonStudio-1.1.5-x86最新版国人开发的python界面ide&#xff0c;可以制作窗体资源-CSDN文库https://download.csdn.net/download/xiaoyao961/88688447 【免费】PythonStudio-1.1.5-x64-Setup.exe国人开发的python界面ide&#xff0c;可以制作窗体资源-CSDN文库https…

2023 北京国炬软件年度总结—JeecgBoot与敲敲云

2023年对于北京国炬软件公司来说是一个充满成就和创新的一年。 我们成功推出了APass零代码平台—敲敲云&#xff0c;一款能够在5分钟内搭建应用的新一代零代码平台。自2023年1月1号正式上线以来&#xff0c;敲敲云已经突破了10万注册用户&#xff0c;并与数百家战略合作伙伴达…

prometheus与zabbix监控的对比介绍

一、普米与zabbix基本介绍 1、prometheus介绍 Prometheus的基本原理是Prometheus Server通过HTTP周期性抓取被监控组件的监控数据&#xff0c;任意组件只要提供对应的HTTP接口并且符合Prometheus定义的数据格式&#xff0c;就可以接入Prometheus监控。 工作流程大致分为收集数…

神经网络的核心:帮助新手理解 PyTorch 非线性激活函数

目录 torch.nn子函数非线性激活详解 nn.Softmin Softmin 函数简介 函数工作原理 参数详解 使用技巧与注意事项 示例代码 nn.Softmax Softmax 函数简介 函数工作原理 参数详解 使用技巧与注意事项 示例代码 nn.Softmax2d Softmax2d 函数简介 函数工作原理 输入…

谈谈我眼中的用户分层分析!

大家好&#xff0c;我是阿粥。 用户分层是几乎所有品牌都会思考的一个重要命题。 合理的分层能够帮助品牌有效提升用户的忠诚度。 这篇文章&#xff0c;我会带大家熟悉用户分层的基本概念、价值以及分层分析的思路。内容略干&#xff0c;在看的时候可以喝两口水。 同样&#xf…

新国大张阳教授团队开发精度远超AlphaFold的AI蛋白质互作结构预测算法

蛋白质是一类由20种不同氨基酸组成的大分子有机化合物&#xff0c;它们是生命体内各种生物活动的最主要的执行者。蛋白质的绝大多数生物学功能都是通过与其它蛋白质的相互作用来实现。因此&#xff0c;蛋白质-蛋白质相互作用&#xff08;简称‘蛋白质互作’&#xff09;在生命功…

C# OpenCvSharp DNN FreeYOLO 密集行人检测

目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN FreeYOLO 密集行人检测 效果 模型信息 Inputs ------------------------- name&#xff1a;input tensor&#xff1a;Float[1, 3, 192, 320] --------------------------------------------------------------- …

在 Linux 中开启 Flask 项目持续运行

在 Linux 中开启 Flask 项目持续运行 在部署 Flask 项目时&#xff0c;情况往往并不是那么理想。默认情况下&#xff0c;关闭 SSH 终端后&#xff0c;Flask 服务就停止了。这时&#xff0c;您需要找到一种方法在 Linux 服务器上实现持续运行 Flask 项目&#xff0c;并在服务器…

<HarmonyOS主题课>1~3课后习题汇总

&#xff1c;HarmonyOS第一课&#xff1e;1~10课后习题汇总 1使用DevEco Studio高效开发 单选题 用哪一种装饰器修饰的组件可作为页面入口组件&#xff1f;&#xff08;B&#xff09; A. ComponentB. EntryC. PreviewD. Builder ArkTS Stage模型支持API Version 9&#xf…

JSP页面访问JDBC数据库的六个步骤

【例】创建exgample11_1.jsp页面&#xff0c;并在该页面中使用纯Java数据库驱动程序连接数据库test&#xff0c;并查询数据表goods中的数据。 <% page language"java" contentType"text/html;charsetUTF-8" pageEncoding"UTF-8"%> <% …

JVM工作原理与实战(六):类的生命周期-连接阶段

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、类的生命周期 1.加载&#xff08;Loading&#xff09; 2.连接&#xff08;Linking&#xff09; 3.初始化&#xff08;Initialization&#xff09; 4.使用&#xff08;Using&…

Linux学习(11)——进程的基本概念

目录 一、程序 1、什么是程序&#xff1f; 二、进程 1、什么是进程&#xff1f; 2、进程具有的特征 2.2进程&#xff0c;线程&#xff0c;协程 2.2.1 进程和线程的区别 2.2.2如何查看一个程序是多线程还是单线程 3、进程使用内存的问题 ①内存泄漏&#xff1a;Memory L…

航天航空线束工艺3D虚拟展馆支持多人异地参观漫游

为了满足汽车线束企业员工工作需要&#xff0c;让新老员工了解到更先进、规范的线束工艺设计技术&#xff0c;华锐视点基于VR虚拟仿真、web3d开发和图形图像技术制作了一款汽车线束工艺设计VR虚拟仿真模拟展示系统。 汽车线束工艺设计VR虚拟仿真模拟展示系统共分为pc电脑端和VR…