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

本文来自#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();}...其他逻辑代码
}...mapStatetoPropsconst 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.hqwc.cn/news/324258.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue2 消息弹框

父页面 <template><div style"margin-top: 20px"><div class"nav-style msg-style"><el-badge :value"value" :max"99" class"num" v-if"value > 0"><i class"el-icon-bell&…

Qt6学习笔记:对象树

使用QObject及其子类创建的对象是以对象树的形式来组织的。创建一个QObject对象时若设置一个父对象&#xff0c;它就会被添加到父对象的子对象列表里&#xff0c;一个父对象被删除时&#xff0c;其全部的子对象都会自动删除。QObject类的构造函数里有一个参数用于设置对象的父对…

gz-hamonic 安装提示缺少许多依赖无法安装

在软件更新源中增加gz-hamonic的软件源&#xff0c; 点击添加&#xff0c;在输入框中填入如下语句&#xff1a; deb http://packages.osrfoundation.org/ubuntu jammy main 如图所示&#xff1a; 然后执行 sudo apt -get install gz-hamonic即可安装。 如下图 在终端中输入…

iOS苹果和Android安卓测试APP应用程序的差异

Hello大家好呀&#xff0c;我是咕噜铁蛋&#xff01;我们经常需要关注移动应用程序的测试和优化&#xff0c;以提供更好的用户体验。在移动应用开发领域&#xff0c;iOS和Android是两个主要的操作系统平台。本文铁蛋讲给各位小伙伴们详细介绍在App测试中iOS和Android的差异&…

Bee的批量插入与事务使用

* Bee 在2.2之前,调用批量插入在每个批都会提交commit,但在2.2改为只调用一次且在事务中,在批量插入的方法内容不再提交,而由事务控制. * * 2.2之前,批量插入使用每一个批次提交一次事务; * 这样,当违反主键约束等就忽略的大批量插入效率是很高的; * 但当事务中有批量插…

Java大数据hadoop2.9.2搭建伪分布式yarn资源管理器

1、修改配置文件 cd /usr/local/hadoop/etc/hadoop cp ./mapred-site.xml.template ./mapred-site.xml vi mapred-site.xml <configuration><property><name>mapreduce.framework.name</name><value>yarn</value></property> &l…

Postman Newman 教程:轻松管理 API 自动化测试步骤

Postman 中的 Newman 是什么&#xff1f; Newman 是一个 CLI&#xff08;命令行界面&#xff09;工具&#xff0c;用于运行 Postman 中的集合&#xff08;Collection&#xff09;和环境&#xff08;Environment&#xff09;来进行自动化测试。它允许直接从命令行运行 Postman …

Babylonjs 6.0文档 Deep Dive - 摄像机介绍(一)

摄像机 在Babylon.js的众多的可用摄像机中&#xff0c;最常用的两种可能是用于“第一人称”运动的通用相机、轨道相机ArcRotateCamera&#xff0c;以及用于现代虚拟现实体验的WebXRCamera。 为了允许用户输入&#xff0c;摄像机必须被附加在canvas中 camera.attachControl(c…

并查集(C++)

目录 一、并查集的原理二、并查集的实现路径压缩 三、并查集的应用结尾 一、并查集的原理 并查集的两个功能&#xff1a; 合并&#xff1a;合并两个不想联系的元素查询&#xff1a;判断两个元素是否在同一个组内 主要解决的是元素分组的问题。 例如&#xff1a;某班级要创建…

求实创新 不断探索 浙江移动基于亚信科技AntDB数据库率先完成CRM系统全域改造

12日20日&#xff0c;中国信息通信研究院&#xff08;简称&#xff1a;信通院&#xff09;和中国通信标准化协会大数据库技术推进委员会&#xff08;CCSA TC601&#xff09;共同组织的2023年大数据库“星河&#xff08;Galaxy&#xff09;”案例评选结果发布。中国移动通信集团…

驱动芯片,预计未来几年将达到605亿美元的规模

驱动芯片是指驱动显示屏幕幕的芯片&#xff0c;其应用广泛&#xff0c;主要用于电脑、手机、电视、平板电脑和汽车等设备。全球驱动芯片市场分析&#xff1a; 据市场研究预测&#xff0c;未来几年内&#xff0c;全球驱动芯片市场将保持稳定增长&#xff0c;并达到605亿美元的规…