React 把一系列 state 更新加入队列
把一系列 state 更新加入队列
设置组件 state 会把一次重新渲染加入队列。但有时你可能会希望在下次渲染加入队列之前对 state 的值执行多次操作。为此,了解 React 如何批量更新 state 会很有帮助。
开发环境:React+ts+antd
学习内容
- 什么是“批处理”以及 React 如何使用它来处理多个 state 更新
- 如何连续多次对同一 state 变量进行更新
React 会对 state 更新进行批处理
在下面的示例中,你可能会认为点击 “+3” 按钮会使计数器递增三次,因为它调用了 setNumber(number + 1) 三次:
import React from 'react';
import {Button} from "antd";
import {useState} from "react";const App: React.FC = () => {const [number, setNumber] = useState(0);return (<div><span style={{marginRight:20}}>{number}</span><Button color="blue" variant="solid" onClick={()=>{setNumber(number + 1);setNumber(number + 1);setNumber(number + 1);}}>+3</Button></div>);
};export default App;
点击按钮,输出结果为"1",并不是"3"

因为每一次渲染的 state 值都是固定的,因此无论你调用多少次 setNumber(1),在第一次渲染的事件处理函数内部的 number 值总是 0
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
在下次渲染前多次更新同一个 state
如果你想在下次渲染之前多次更新同一个 state,你可以像 setNumber(n => n + 1) 这样传入一个根据队列中的前一个 state 计算下一个 state 的 函数,而不是像 setNumber(number + 1) 这样传入 下一个 state 值。这是一种告诉 React “用 state 值做某事”而不是仅仅替换它的方法。
现在尝试递增计数器:
import React from 'react';
import {Button} from "antd";
import {useState} from "react";const App: React.FC = () => {const [number, setNumber] = useState(0);return (<div><span style={{marginRight:20}}>{number}</span><Button color="blue" variant="solid" onClick={()=>{setNumber(n=>n + 1);setNumber(n=>n + 1);setNumber(n=>n + 1);}}>+3</Button></div>);
};export default App;
点击按钮,输出结果:

在这里,n => n + 1 被称为 更新函数。当你将它传递给一个 state 设置函数时:
React 会将此函数加入队列,以便在事件处理函数中的所有其他代码运行后进行处理。
在下一次渲染期间,React 会遍历队列并给你更新之后的最终 state。
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
下面是 React 在执行事件处理函数时处理这几行代码的过程:
- setNumber(n => n + 1):n => n + 1 是一个函数。React 将它加入队列。
- setNumber(n => n + 1):n => n + 1 是一个函数。React 将它加入队列。
- setNumber(n => n + 1):n => n + 1 是一个函数。React 将它加入队列。
当你在下次渲染期间调用 useState 时,React 会遍历队列。之前的 number state 的值是 0,所以这就是 React 作为参数 n 传递给第一个更新函数的值。然后 React 会获取你上一个更新函数的返回值,并将其作为 n 传递给下一个更新函数,以此类推:
| 更新队列 | n | 返回值 |
|---|---|---|
| n => n + 1 | 0 | 0 + 1 = 1 |
| n => n + 1 | 1 | 1 + 1 = 2 |
| n => n + 1 | 2 | 2 + 1 = 3 |
React 会保存 3 为最终结果并从 useState 中返回。
这就是为什么在上面的示例中点击“+3”正确地将值增加“+3”。
如果你在替换 state 后更新 state 会发生什么
import React from 'react';
import {Button} from "antd";
import {useState} from "react";const App: React.FC = () => {const [number, setNumber] = useState(0);return (<div><span style={{marginRight:20}}>{number}</span><Button color="blue" variant="solid" onClick={()=>{setNumber(number + 3);setNumber(n=>n + 1);}}>增加数字</Button></div>);
};export default App;
点击按钮,输出结果为:

这是事件处理函数告诉 React 要做的事情:
setNumber(number + 3):number 为 0,所以 setNumber(0 + 3)。React 将 “替换为 3” 添加到其队列中。
setNumber(n => n + 1):n => n + 1 是一个更新函数。 React 将 该函数 添加到其队列中。
在下一次渲染期间,React 会遍历 state 队列:
| 更新队列 | n | 返回值 |
|---|---|---|
| “替换为 3” | 0(未使用) | 3 |
| n => n + 1 | 3 | 3 + 1 = 4 |
React 会保存 4 为最终结果并从 useState 中返回。
如果你在更新 state 后替换 state 会发生什么
让我们再看一个例子。你认为 number 在下一次渲染中的值是什么?
<button onClick={() => {
import React from 'react';
import {Button} from "antd";
import {useState} from "react";const App: React.FC = () => {const [number, setNumber] = useState(0);return (<div><span style={{marginRight:20}}>{number}</span><Button color="blue" variant="solid" onClick={()=>{setNumber(number + 5);setNumber(n=>n + 1);setNumber(42);}}>增加数字</Button></div>);
};export default App;
}}>
输出

以下是 React 在执行事件处理函数时处理这几行代码的过程:
setNumber(number + 5):number 为 0,所以 setNumber(0 + 5)。React 将 “替换为 5” 添加到其队列中。
setNumber(n => n + 1):n => n + 1 是一个更新函数。React 将该函数添加到其队列中。
setNumber(42):React 将 “替换为 42” 添加到其队列中。
在下一次渲染期间,React 会遍历 state 队列:
| 更新队列 | n | 返回值 |
|---|---|---|
| “替换为 5” | 0(未使用) | 5 |
| n => n + 1 | 5 | 5 + 1 = 6 |
| “替换为 42” | 6(未使用) | 42 |
然后 React 会保存 42 为最终结果并从 useState 中返回。
总而言之,以下是你可以考虑传递给 setNumber state 设置函数的内容:
- 一个更新函数(例如:n => n + 1)会被添加到队列中。
- 任何其他的值(例如:数字 5)会导致“替换为 5”被添加到队列中,已经在队列中的内容会被忽略。
事件处理函数执行完成后,React 将触发重新渲染。在重新渲染期间,React 将处理队列。更新函数会在渲染期间执行,因此 更新函数必须是 纯函数 并且只 返回 结果。不要尝试从它们内部设置 state 或者执行其他副作用。在严格模式下,React 会执行每个更新函数两次(但是丢弃第二个结果)以便帮助你发现错误。
摘要
- 设置 state 不会更改现有渲染中的变量,但会请求一次新的渲染。
- React 会在事件处理函数执行完成之后处理 state 更新。这被称为批处理。
- 要在一个事件中多次更新某些 state,你可以使用 setNumber(n => n + 1) 更新函数。
接下来,我们实现一个计数器,实现每次用户按下“购买”按钮,“等待”计数器应该增加一。3秒后,“等待”计数器应该减少,“完成”计数器应该增加。
import React from 'react';
import {Button} from "antd";
import {useState} from "react";const App: React.FC = () => {const [pending, setPending] = useState(0);const [completed, setCompleted] = useState(0);// 定义 delay 函数const delay=(ms: number)=> {return new Promise(resolve => setTimeout(resolve, ms));}async function handleClick() {setPending(p=>p + 1);await delay(3000);setPending(p=>p - 1);setCompleted(c=>c + 1);}return (<div><h3>等待:{pending}</h3><h3>完成:{completed}</h3><Button color="blue" variant="solid" onClick={handleClick}>增加数字</Button></div>);
};export default App;
相关文章:
React 把一系列 state 更新加入队列
把一系列 state 更新加入队列 设置组件 state 会把一次重新渲染加入队列。但有时你可能会希望在下次渲染加入队列之前对 state 的值执行多次操作。为此,了解 React 如何批量更新 state 会很有帮助。 开发环境:Reacttsantd 学习内容 什么是“批处理”以…...
【大模型理论篇】Search-R1: 通过强化学习训练LLM推理与利⽤搜索引擎
最近基于强化学习框架来实现大模型在推理和检索能力增强的项目很多,也是Deep Research技术持续演进的缩影。之前我们讨论过《R1-Searcher:通过强化学习激励llm的搜索能⼒》,今天我们分析下Search-R1【1】。 1. 研究背景与问题 ⼤模型(LLM&a…...
Google政策大更新:影响金融,新闻,社交等所有类别App
Google Play 4月10日 迎来了2025年第一次大版本更新,新政主要涉及金融(个人贷款),新闻两个行业。但澄清内容部分却使得所有行业都需进行一定的更新。下面,我们依次从金融(个人贷款),…...
什么时候触发full GC(发生场景)
文章目录 1. 老年代空间不足2. 分配担保失败3. 显式调用`System.gc()`4. 元空间/永久代空间不足5. CMS/G1的并发失败6. 空间分配担保机制7. 堆内存碎片化8. 其他场景总结回答在Java中,Full GC(全局垃圾回收)会回收整个堆内存(包括年轻代、老年代)以及元空间(或永久代)。…...
NO.93十六届蓝桥杯备战|图论基础-拓扑排序|有向无环图|AOV网|摄像头|最大食物链计数|杂物(C++)
有向⽆环图 若⼀个有向图中不存在回路,则称为有向⽆环图(directed acycline graph),简称 DAG 图 AOV⽹ 举⼀个现实中的例⼦:课程的学习是有优先次序的,如果规划不当会严重影响学习效果。课程间的先后次序可以⽤有向图表⽰ 在…...
每日文献(十三)——Part one
今天看的是《RefineNet: Iterative Refinement for Accurate Object Localization》。 目录 零、摘要 0.1 原文 0.2 译文 一、介绍 二、RefineNet A. Fast R-CNN B. Faster R-CNN C. RefineNet 训练 D. RefineNet 测试 零、摘要 0.1 原文 We investigate a new str…...
游戏引擎学习第225天
只能说太难了 回顾当前的进度 我们正在进行一个完整游戏的开发,并在直播中同步推进。上周我们刚刚完成了过场动画系统的初步实现,把开场动画基本拼接完成,整体效果非常流畅。看到动画顺利呈现,令人十分满意,整个系统…...
git提取出指定提交所涉及的所有文件
当需要提取出某次提交所修改过的所有的文件时,可以使用如下命令,该命令来自文心一言 mkdir temp_dir git diff-tree --no-commit-id --name-only -r <commit-hash> | xargs -I {} cp --parents {} temp_dir/--no-commit-id:不显示提交…...
Linux 使用Nginx搭建简易网站模块
网站需求: 一、基于域名[www.openlab.com](http://www.openlab.com)可以访问网站内容为 welcome to openlab 二、给该公司创建三个子界面分别显示学生信息,教学资料和缴费网站,基于[www.openlab.com/student](http://www.openlab.com/stud…...
抖音ai无人直播间助手场控软件
获取API权限 若使用DeepSeek官方AI服务,登录其开发者平台申请API Key或Token。 若为第三方AI(如ChatGPT),需通过接口文档获取访问权限。 配置场控软件 打开DeepSeek场控软件,进入设置界面找到“AI助手”或“自动化”…...
深度解析Redis过期字段清理机制:从源码到集群化实践 (二)
本文紧跟 上一篇 深度解析Redis过期字段清理机制:从源码到集群化实践 (一) 可以从redis合集中查看 八、Redis内核机制深度解析 8.1 Lua脚本执行引擎原理 Lua脚本执行流程图技术方案 执行全流程解析: #mermaid-svg-X51Gno…...
TCP标志位抓包
说明 TCP协议的Header信息,URG、ACK、PSH、RST、SYN、FIN这6个字段在14字节的位置,对应的是tcp[13],因为字节数是从[0]开始数的,14字节对应的就是tcp[13],因此在抓这几个标志位的数据包时就要明确范围在tcp[13] 示例1…...
如何实现动态请求地址(baseURL)
需求: 在项目中遇到了需要实时更换请求地址,后续使用修改后的请求地址(IP) 例如:原ip请求为http://192.168.1.1:80/xxx,现在需要你点击或其他操作将其修改为http://192.168.1.2:80/xxx,该如何操作 tips: 修改后需要跳转( 修改了IP之前的不可使用,需要访问修改后的地址来操作 …...
封装一个搜索区域 SearchForm.vue组件
父组件 <template><div><SearchForm:form-items"searchItems":initial-values"initialValues"search"handleSearch"reset"handleReset"><!-- 自定义插槽内容 --><template #custom-slot"{ form }&qu…...
《ADVANCING MATHEMATICAL REASONING IN LAN- GUAGE MODELS》全文阅读
《ADVANCING MATHEMATICAL REASONING IN LAN- GUAGE MODELS: THE IMPACT OF PROBLEM-SOLVING DATA, DATA SYNTHESIS METHODS, AND TRAINING STAGES》全文阅读 提升语言模型中的数学推理能力:问题求解数据、数据合成方法及训练阶段的影响 \begin{abstract} 数学推…...
Day56 | 99. 恢复二叉搜索树、103. 二叉树的锯齿形层序遍历、109. 有序链表转换二叉搜索树、113. 路径总和 II
99. 恢复二叉搜索树 题目链接:99. 恢复二叉搜索树 - 力扣(LeetCode) 题目难度:中等 代码: class Solution {public void recoverTree(TreeNode root) {List<TreeNode> listnew ArrayList<>();dfs(root,…...
GPT - GPT(Generative Pre-trained Transformer)模型框架
本节代码主要为实现了一个简化版的 GPT(Generative Pre-trained Transformer)模型。GPT 是一种基于 Transformer 架构的语言生成模型,主要用于生成自然语言文本。 1. 模型结构 初始化部分 class GPT(nn.Module):def __init__(self, vocab…...
前端加密的几种方式
前端加密的几种方式 一、对称加密原理常用算法代码示例(AES)适用场景 二、非对称加密原理常用算法代码示例(RSA)适用场景 三、哈希函数原理常用算法代码示例(SHA-256)适用场景 四、Base64 编码原…...
贪心算法:部分背包问题深度解析
简介: 该Java代码基于贪心算法实现了分数背包问题的求解,核心通过单位价值降序排序和分阶段装入策略实现最优解。首先对Product数组执行双重循环冒泡排序,按wm(价值/重量比)从高到低重新排列物品;随后分两阶段装入:循环…...
连接器电镀层的作用与性能
连接器电镀层的作用与性能: 镀金 金具有很高的化学稳定性,只溶于王水,不溶于其它酸,金镀层耐蚀性强,导电性好,易于焊接,耐高温,硬金具有一定的耐磨性。 对钢、铜、银及其合金基体而…...
神经网络如何表示数据
神经网络是如何工作的?这是一个让新手和专家都感到困惑的问题。麻省理工学院计算机科学和人工智能实验室(CSAIL)的一个团队表示,理解这些表示,以及它们如何为神经网络从数据中学习的方式提供信息,对于提高深…...
【双指针】和为 s 的两个数字(easy)
和为 s 的两个数字(easy) 题⽬描述:解法⼀(暴⼒解法,会超时):解法⼆(双指针 - 对撞指针):算法思路:C 算法代码Java 算法代码: 题⽬链接…...
.net core 工作流介绍
WikeFlow2.0介绍 WikeFlow官网:http://www.wikesoft.com WikeFlow学习版演示地址:http://workflow.wikesoft.com WikeFlow学习版源代码下载:WorkFlow: 多数据库支持,同时支持:SQLServer,Mysql࿰…...
nginx自编译重现gzip和chunked的现象
前言 最近做项目,发现一个比较好玩的事,nginx的module gzip模式默认支持1KB压缩,和chunked返回,本来现在的很多框架都很完善了,但是,一些新语言框架或者一些老旧框架会不能完整支持chunked,导致…...
jspm企业采购管理系统的设计与实现(源码+lw+部署文档+讲解),源码可白嫖!
摘要 相比于以前的传统企业采购手工管理方式,智能化的管理方式可以大幅降低企业采购管理的运营人员成本,实现了企业采购管理的标准化、制度化、程序化的管理,有效地防止了物资信息、物资入库、出库等的随意管理,提高了信息的处理…...
iOS应用开发指南
开发一款iOS应用是一个系统化的过程,涵盖从环境搭建、界面设计、编码实现到测试发布的各个环节。以下是一份面向初学者的iOS移动应用开发指南,帮助你从零开始构建自己的App。 一、准备工作:开发环境与工具 必备设备 Mac电脑:iO…...
Go之defer关键字:优雅的资源管理与执行控制
在Go语言中,defer关键字是处理资源释放、错误恢复和代码逻辑清理的利器。它看似简单,却隐藏着许多设计哲学和底层机制。本文将深入剖析defer的执行原理、使用场景和常见陷阱,助你掌握这一关键特性。 一、defer基础:延迟执行的本质…...
现代测试自动化框架教程:Behave接口测试与Airtest移动端UI自动化
前言 我发现每天还是陆陆续续有人在看我之前写的自动化框架搭建的文档;即使很早就有新的框架,更好的选择出来了;所以特别写了这一篇目前大厂也在使用的;日活400w有实际落地的自动化测试架构方案; 随着测试技术…...
优化运营、降低成本、提高服务质量的智慧物流开源了
智慧物流视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本可通过边缘计算技术…...
使用Lombok的@Slf4j和idea构建:找不到log符号-解决
问题:在使用Lombok的Slf4j构建项目时提示如下内容: MvcConfiguration.java:26:9 java: cannot find symbol symbol: variable log location: class cn.edu.wynu.mrcinerec.mrserver.config.WebMvcConfiguration查了网上的方法都是改配置 但是使用Googl…...
