当前位置: 首页 > news >正文

【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合并中间件的源码流程。

相关文章:

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

本文来自#React系列教程&#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. 中间件的使用 1.1. 组件中异步请求 在之前简单的案例中&#xff0c;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 过程 查看版本&#xff1a;nginx -v 安装路径&#xff1a;whereis nginx nginx文件安装完成之后的文件位置&#xff1a; /usr/sbin/nginx&#xf…...

【LMM 010】MiniGPT-v2:使用独特的标识符实现视觉语言多任务学习的统一的多模态大模型

论文标题&#xff1a;MiniGPT-v2: Large Language Model As a Unified Interface for Vision-Language Multi-task Learning 论文作者&#xff1a;Jun Chen, Deyao Zhu, Xiaoqian Shen, Xiang Li, Zechun Liu, Pengchuan Zhang, Raghuraman Krishnamoorthi, Vikas Chandra, Yun…...

人工智能如何重塑金融服务业

在体验优先的世界中识别金融服务业中的AI使用场景 人工智能&#xff08;AI&#xff09;作为主要行业的大型组织的重要业务驱动力&#xff0c;持续受到关注。众所周知&#xff0c;传统金融服务业在采用新技术方面相对滞后&#xff0c;一些组织使用的还是上世纪50年代和60年代发…...

Iterable 对象转换为 Stream 对象

在 Java 8 中&#xff0c;可以使用 Stream API 来对集合进行操作。要将 Iterable 对象转换为 Stream 对象&#xff0c;可以使用 StreamSupport 类的 stream() 方法。具体来说&#xff0c;可以按照以下步骤进行转换&#xff1a; 调用 Spliterators.spliteratorUnknownSize(iter…...

基于Java+SpringBoot+vue+elementUI私人健身教练预约管理系统设计实现

基于JavaSpringBootvueelementUI私人健身教练预约管理系统设计实现 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文章目录 基于JavaSpringBootvueelementUI私人健身教练预约管理系统设计实现一、前言介绍&#xff1a;二、系统设计&#xff1a;2.1 性能需求分析2.2 B/S架构&…...

2024,启动(回顾我的2023)

零.前言 打开博客想写个年度总结&#xff0c;发现已经半年没有更新文章了&#xff0c;排名从几千掉到了几万&#xff0c;不过数据量还是不错的。 时间过得可真快&#xff0c;2023年是充满动荡的一年&#xff0c;上半年gpt横空出世&#xff0c;下半年各种翻车暴雷吃瓜吃到嘴软…...

Web网页开发-盒模型-笔记

1.CSS的三种显示方式 (1)块级元素:标签所占区域默认为一行 特点&#xff1a;一行一个 可设宽高 (2)行内元素&#xff1a;标签所占区域由内容顶开&#xff0c;行内元素无法使用text-align 特点&#xff1a;一行多个 不可设宽高&#xff0c;margin上下和padding上下都不能改变位…...

Java打成压缩包的方法汇总

文章目录 1.将指定目录下的文件打包成 .zip2.将指定目录下的文件打包成 .tar.gz3.将指定目录下的文件打包成 .tar4.将指定目录下的文件打包成 .rar5.生成若干个txt并打包到zip中 1.将指定目录下的文件打包成 .zip 代码示例&#xff1a; import java.io.*; import java.util.z…...

2023年第2季社区Task挑战赛贡献者榜单

基于FISCO BCOS及Weldentity&#xff0c;实现SSO单点登录服务&#xff1b;提供食品溯源、电商运费险7天退保、电子病历等智能合约库业务场景案例&#xff1b;基于FISCO BCOS更新游戏体验&#xff1b;体验并分析解读最新发布的分布式数据协作管理解决方案DDCMS&#xff0c;提供相…...

Clickhouse 为什么快

ClickHouse是一个用于联机分析处理&#xff08;OLAP&#xff09;的开源列式数据库管理系统&#xff08;DBMS&#xff09;。它之所以能提供出色的查询性能和处理速度&#xff0c;主要归功于以下几个方面的设计和优化&#xff1a; 列式存储 ClickHouse存储数据按列而不是按行组织…...

【React系列】react-router

本文来自#React系列教程&#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. 认识react-router 1.2. 前端路由原理 前端路由是如何做到URL和内容进行映射呢&#xff1f;监听URL的改变。 UR…...

[数据集][目标检测]车辆检测数据集VOC+YOLO格式1.6w张3类别

一共分为3个压缩包&#xff1a; 【车辆检测数据集AVOCYOLO格式5423张3类别】 数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;5423 标注数…...

FindMy技术用于鼠标

鼠标是计算机的标准配置之一&#xff0c;其设计初衷是为了使计算机的操作更加简便快捷&#xff0c;减少用户在操作中的负担。用户可以通过移动鼠标&#xff0c;实现光标的精确移动&#xff0c;进而选择、拖拽、复制、粘贴等操作。这种操作方式&#xff0c;使得计算机的操作变得…...

已解决‘ping‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。”的问题

已解决‘ping‘ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。”的问题 文章目录 问题介绍 问题分析 解决思路 解决方法 检查并修复环境变量 进入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 是一个流行的性能测试工具&#xff0c;用于测试 Web 应用程序的性能和负载。它通常与 GUI 一起使用&#xff0c;但如果您想在自动化测试中集成 JMeter&#xff0c;或者以编程方式创建和运行测试计划&#xff0c;那么 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…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例

文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

Java + Spring Boot + Mybatis 实现批量插入

在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法&#xff1a;使用 MyBatis 的 <foreach> 标签和批处理模式&#xff08;ExecutorType.BATCH&#xff09;。 方法一&#xff1a;使用 XML 的 <foreach> 标签&#xff…...

动态 Web 开发技术入门篇

一、HTTP 协议核心 1.1 HTTP 基础 协议全称 &#xff1a;HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09; 默认端口 &#xff1a;HTTP 使用 80 端口&#xff0c;HTTPS 使用 443 端口。 请求方法 &#xff1a; GET &#xff1a;用于获取资源&#xff0c;…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

MinIO Docker 部署:仅开放一个端口

MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...