【React系列】Redux(二)中间件
本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg5MDAzNzkwNA==&action=getalbum&album_id=1566025152667107329)
一. 中间件的使用
1.1. 组件中异步请求
在之前简单的案例中,redux中保存的counter是一个本地定义的数据,我们可以直接通过同步的操作来dispatch action,state就会被立即更新。
但是真实开发中,redux中保存的很多数据可能来自服务器,我们需要进行异步的请求,再将数据保存到redux中。
在之前学习网络请求的时候我们讲过,网络请求可以在class组件的componentDidMount中发送,所以我们可以有这样的结构:

我现在完成如下案例操作:
- 在
Home组件中请求banners和recommends的数据; - 在
Profile组件中展示banners和recommends的数据;
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)的概念:
- 这个中间件的目的是在
dispatch的action和最终达到的reducer之间,扩展一些自己的代码; - 比如日志记录、调用异步接口、添加代码调试功能等等;
我们现在要做的事情就是发送异步的网络请求,所以我们可以添加对应的中间件:
- 这里官网推荐的、包括演示的网络请求的中间件是使用
redux-thunk;
redux-thunk是如何做到让我们可以发送异步的请求呢?
- 我们知道,默认情况下的
dispatch(action),action需要是一个JavaScript的对象; redux-thunk可以让dispatch(action函数),action可以是一个函数;- 该函数会被调用,并且会传给这个函数一个
dispatch函数和getState函数;dispatch函数用于我们之后再次派发action;getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态;
如何使用 redux-thunk 呢?
- 安装 redux-thunk
yarn add redux-thunk
- 在创建
store时传入应用了middleware的enhance函数
- 通过
applyMiddleware来结合多个Middleware, 返回一个enhancer; - 将
enhancer作为第二个参数传入到createStore中;
// 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
const enhancer = applyMiddleware(thunkMiddleware);
// 将enhancer作为第二个参数传入到createStore中
const store = createStore(reducer, enhancer);
- 定义返回一个函数的
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之后会被执行;
- 修改
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对象
调用iterator的next函数,会销毁一次迭代器,并且返回一个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
generator和promise一起使用:
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的使用
- 装 redux-saga
yarn add redux-saga
- 集成 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;
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合并中间件的源码流程。
相关文章:
【React系列】Redux(二)中间件
本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. 中间件的使用 1.1. 组件中异步请求 在之前简单的案例中,redux中保存的counter是一个本地定义的数据…...
YOLOv8改进 | 2023Neck篇 | 利用Gold-YOLO改进YOLOv8对小目标检测
一、本文介绍 本文给大家带来的改进机制是Gold-YOLO利用其Neck改进v8的Neck,GoLd-YOLO引入了一种新的机制——信息聚集-分发(Gather-and-Distribute, GD)。这个机制通过全局融合不同层次的特征并将融合后的全局信息注入到各个层级中,从而实现更高效的信息交互和融合。这种…...
ubuntu环境安装配置nginx流程
今天分享ubuntu环境安装配置nginx流程 一、下载安装 1、检查是否已经安装 nginx -v 结果 2、安装 apt install nginx-core 过程 查看版本:nginx -v 安装路径:whereis nginx nginx文件安装完成之后的文件位置: /usr/sbin/nginx…...
【LMM 010】MiniGPT-v2:使用独特的标识符实现视觉语言多任务学习的统一的多模态大模型
论文标题:MiniGPT-v2: Large Language Model As a Unified Interface for Vision-Language Multi-task Learning 论文作者:Jun Chen, Deyao Zhu, Xiaoqian Shen, Xiang Li, Zechun Liu, Pengchuan Zhang, Raghuraman Krishnamoorthi, Vikas Chandra, Yun…...
人工智能如何重塑金融服务业
在体验优先的世界中识别金融服务业中的AI使用场景 人工智能(AI)作为主要行业的大型组织的重要业务驱动力,持续受到关注。众所周知,传统金融服务业在采用新技术方面相对滞后,一些组织使用的还是上世纪50年代和60年代发…...
Iterable 对象转换为 Stream 对象
在 Java 8 中,可以使用 Stream API 来对集合进行操作。要将 Iterable 对象转换为 Stream 对象,可以使用 StreamSupport 类的 stream() 方法。具体来说,可以按照以下步骤进行转换: 调用 Spliterators.spliteratorUnknownSize(iter…...
基于Java+SpringBoot+vue+elementUI私人健身教练预约管理系统设计实现
基于JavaSpringBootvueelementUI私人健身教练预约管理系统设计实现 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文章目录 基于JavaSpringBootvueelementUI私人健身教练预约管理系统设计实现一、前言介绍:二、系统设计:2.1 性能需求分析2.2 B/S架构&…...
2024,启动(回顾我的2023)
零.前言 打开博客想写个年度总结,发现已经半年没有更新文章了,排名从几千掉到了几万,不过数据量还是不错的。 时间过得可真快,2023年是充满动荡的一年,上半年gpt横空出世,下半年各种翻车暴雷吃瓜吃到嘴软…...
Web网页开发-盒模型-笔记
1.CSS的三种显示方式 (1)块级元素:标签所占区域默认为一行 特点:一行一个 可设宽高 (2)行内元素:标签所占区域由内容顶开,行内元素无法使用text-align 特点:一行多个 不可设宽高,margin上下和padding上下都不能改变位…...
Java打成压缩包的方法汇总
文章目录 1.将指定目录下的文件打包成 .zip2.将指定目录下的文件打包成 .tar.gz3.将指定目录下的文件打包成 .tar4.将指定目录下的文件打包成 .rar5.生成若干个txt并打包到zip中 1.将指定目录下的文件打包成 .zip 代码示例: import java.io.*; import java.util.z…...
2023年第2季社区Task挑战赛贡献者榜单
基于FISCO BCOS及Weldentity,实现SSO单点登录服务;提供食品溯源、电商运费险7天退保、电子病历等智能合约库业务场景案例;基于FISCO BCOS更新游戏体验;体验并分析解读最新发布的分布式数据协作管理解决方案DDCMS,提供相…...
Clickhouse 为什么快
ClickHouse是一个用于联机分析处理(OLAP)的开源列式数据库管理系统(DBMS)。它之所以能提供出色的查询性能和处理速度,主要归功于以下几个方面的设计和优化: 列式存储 ClickHouse存储数据按列而不是按行组织…...
【React系列】react-router
本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. 认识react-router 1.2. 前端路由原理 前端路由是如何做到URL和内容进行映射呢?监听URL的改变。 UR…...
[数据集][目标检测]车辆检测数据集VOC+YOLO格式1.6w张3类别
一共分为3个压缩包: 【车辆检测数据集AVOCYOLO格式5423张3类别】 数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):5423 标注数…...
FindMy技术用于鼠标
鼠标是计算机的标准配置之一,其设计初衷是为了使计算机的操作更加简便快捷,减少用户在操作中的负担。用户可以通过移动鼠标,实现光标的精确移动,进而选择、拖拽、复制、粘贴等操作。这种操作方式,使得计算机的操作变得…...
已解决‘ping‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。”的问题
已解决‘ping‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。”的问题 文章目录 问题介绍 问题分析 解决思路 解决方法 检查并修复环境变量 进入c:\windows\system32再ping 使用系统工具修复系统文件 Q1 - 问题介绍 当您尝试在Windows命令提示符下…...
基于PGPGPOOL-II部署PostgreSQL高可用环境
PGPOOL-II是一个位于PostgreSQL服务器和 PostgreSQL 数据库客户端之间的中间件,具有以下功能: 1. 连接池:PGPOOL-II可以保持已经连接到 PostgreSQL 服务器的连接,并在使用相同参数(例如:用户名、数据库、协议版本)连接进来时重用它们。这可以减少连接开销,并增加系统的…...
【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 运行环境搭建
【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 概述-CSDN博客 【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 运行环境搭建-CSDN博客 【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 运行模式-CSDN博客 1、模板虚拟机环境准备 1.1、 hadoop100 虚拟机配置要求如下 &…...
Python 操作 JMeter 探索:pymeter 实操指南
概要 JMeter 是一个流行的性能测试工具,用于测试 Web 应用程序的性能和负载。它通常与 GUI 一起使用,但如果您想在自动化测试中集成 JMeter,或者以编程方式创建和运行测试计划,那么 pymeter 库将是一个强大的工具。本文将介绍如何…...
微软 Power Platform 使用Power Automate发送邮件以Dataverse作为数据源的附件File Column
微软Power Platform使用Power Automate发送邮件添加Power Apps以Dataverse作为数据源的附件File Column方式 目录 微软Power Platform使用Power Automate发送邮件添加Power Apps以Dataverse作为数据源的附件File Column方式1、需求背景介绍2、附件列File Column介绍3、如何在Po…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
