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

问:React的useState和setState到底是同步还是异步呢?

先来思考一个老生常谈的问题,setState是同步还是异步?

再深入思考一下,useState是同步还是异步呢?

我们来写几个 demo 试验一下。

先看 useState

同步和异步情况下,连续执行两个 useState 示例

function Component() {const [a, setA] = useState(1)const [b, setB] = useState('b')console.log('render')function handleClickWithPromise() {Promise.resolve().then(() => {setA((a) => a + 1)setB('bb')})}function handleClickWithoutPromise() {setA((a) => a + 1)setB('bb')}return (<Fragment><button onClick={handleClickWithPromise}>{a}-{b} 异步执行      </button><button onClick={handleClickWithoutPromise}>{a}-{b} 同步执行      </button></Fragment>)
}

结论:

  • 当点击同步执行按钮时,只重新 render 了一次
  • 当点击异步执行按钮时,render 了两次

同步和异步情况下,连续执行两次同一个 useState 示例

function Component() {const [a, setA] = useState(1)console.log('a', a)function handleClickWithPromise() {Promise.resolve().then(() => {setA((a) => a + 1)setA((a) => a + 1)})}function handleClickWithoutPromise() {setA((a) => a + 1)setA((a) => a + 1)}return (<Fragment><button onClick={handleClickWithPromise}>{a} 异步执行</button><button onClick={handleClickWithoutPromise}>{a} 同步执行</button></Fragment>)
}
  • 当点击同步执行按钮时,两次 setA 都执行,但合并 render 了一次,打印 3
  • 当点击异步执行按钮时,两次 setA 各自 render 一次,分别打印 2,3

参考 前端进阶面试题详细解答

再看 setState

同步和异步情况下,连续执行两个 setState 示例

class Component extends React.Component {constructor(props) {super(props)this.state = {a: 1,b: 'b',}}handleClickWithPromise = () => {Promise.resolve().then(() => {this.setState({...this.state, a: 'aa'})this.setState({...this.state, b: 'bb'})})}handleClickWithoutPromise = () => {this.setState({...this.state, a: 'aa'})this.setState({...this.state, b: 'bb'})}render() {console.log('render')return (<Fragment><button onClick={this.handleClickWithPromise}>异步执行</button><button onClick={this.handleClickWithoutPromise}>同步执行</button></Fragment>)}
}
  • 当点击同步执行按钮时,只重新 render 了一次
  • 当点击异步执行按钮时,render 了两次

跟useState的结果一样

同步和异步情况下,连续执行两次同一个 setState 示例

class Component extends React.Component {constructor(props) {super(props)this.state = {a: 1,}}handleClickWithPromise = () => {Promise.resolve().then(() => {this.setState({a: this.state.a + 1})this.setState({a: this.state.a + 1})})}handleClickWithoutPromise = () => {this.setState({a: this.state.a + 1})this.setState({a: this.state.a + 1})}render() {console.log('a', this.state.a)return (<Fragment><button onClick={this.handleClickWithPromise}>异步执行</button><button onClick={this.handleClickWithoutPromise}>同步执行</button></Fragment>)}
}
  • 当点击同步执行按钮时,两次 setState 合并,只执行了最后一次,打印 2
  • 当点击异步执行按钮时,两次 setState 各自 render 一次,分别打印 2,3

这里跟useState不同,同步执行时useState也会对state进行逐个处理,而setState则只会处理最后一次

为什么会有同步执行和异步执行结果不同呢?

这里就涉及到 react 的 batchUpdate 机制,合并更新。

  • 首先,为什么需要合并更新呢?

如果没有合并更新,在每次执行 useState 的时候,组件都要重新 render 一次,会造成无效渲染,浪费时间(因为最后一次渲染会覆盖掉前面所有的渲染效果)。
所以 react 会把一些可以一起更新的 useState/setState 放在一起,进行合并更新。

  • 怎么进行合并更新

这里 react 用到了事务机制。

React 中的 Batch Update 是通过「Transaction」实现的。在 React 源码关于 Transaction 的部分,用一大段文字及一幅字符画解释了 Transaction 的作用:

*                       wrappers (injected at creation time)
*                                      +        +
*                                      |        |
*                    +-----------------|--------|--------------+
*                    |                 v        |              |
*                    |      +---------------+   |              |
*                    |   +--|    wrapper1   |---|----+         |
*                    |   |  +---------------+   v    |         |
*                    |   |          +-------------+  |         |
*                    |   |     +----|   wrapper2  |--------+   |
*                    |   |     |    +-------------+  |     |   |
*                    |   |     |                     |     |   |
*                    |   v     v                     v     v   | wrapper
*                    | +---+ +---+   +---------+   +---+ +---+ | invariants
* perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
*                    | |   | |   |   |         |   |   | |   | |
*                    | |   | |   |   |         |   |   | |   | |
*                    | |   | |   |   |         |   |   | |   | |
*                    | +---+ +---+   +---------+   +---+ +---+ |
*                    |  initialize                    close    |
*                    +-----------------------------------------+

用大白话说就是在实际的 useState/setState 前后各加了段逻辑给包了起来。只要是在同一个事务中的 setState 会进行合并(注意,useState不会进行state的合并)处理。

  • 为什么 setTimeout 不能进行事务操作

由于 react 的事件委托机制,调用 onClick 执行的事件,是处于 react 的控制范围的。

而 setTimeout 已经超出了 react 的控制范围,react 无法对 setTimeout 的代码前后加上事务逻辑(除非 react 重写 setTimeout)。

所以当遇到 setTimeout/setInterval/Promise.then(fn)/fetch 回调/xhr 网络回调时,react 都是无法控制的。

相关react 源码如下:

if (executionContext === NoContext) {// Flush the synchronous work now, unless we're already working or inside// a batch. This is intentionally inside scheduleUpdateOnFiber instead of// scheduleCallbackForFiber to preserve the ability to schedule a callback// without immediately flushing it. We only do this for user-initiated// updates, to preserve historical behavior of legacy mode.flushSyncCallbackQueue()
}

executionContext 代表了目前 react 所处的阶段,而 NoContext 你可以理解为是 react 已经没活干了的状态。而 flushSyncCallbackQueue 里面就会去同步调用我们的 this.setState ,也就是说会同步更新我们的 state 。所以,我们知道了,当 executionContext 为 NoContext 的时候,我们的 setState 就是同步的

总结

我们来总结一下上述实验的结果:

  1. 在正常的react的事件流里(如onClick等)
  • setState和useState是异步执行的(不会立即更新state的结果)
  • 多次执行setState和useState,只会调用一次重新渲染render
  • 不同的是,setState会进行state的合并,而useState则不会
  1. 在setTimeout,Promise.then等异步事件中
  • setState和useState是同步执行的(立即更新state的结果)
  • 多次执行setState和useState,每一次的执行setState和useState,都会调用一次render

是不是感觉有点绕,自己写一下代码体验一下就好了~

相关文章:

问:React的useState和setState到底是同步还是异步呢?

先来思考一个老生常谈的问题&#xff0c;setState是同步还是异步? 再深入思考一下&#xff0c;useState是同步还是异步呢&#xff1f; 我们来写几个 demo 试验一下。 先看 useState 同步和异步情况下&#xff0c;连续执行两个 useState 示例 function Component() {const…...

深度理解机器学习16-门控循环单元

评估简单循环神经网络的缺点。 描述门控循环单元&#xff08;Gated Recurrent Unit&#xff0c;GRU&#xff09;的架构。 使用GRU进行情绪分析。 将GRU应用于文本生成。 基本RNN通常由输入层、输出层和几个互连的隐藏层组成。最简单的RNN有一个缺点&#xff0c;那就是它们不…...

Python中Generators教程

要想创建一个iterator&#xff0c;必须实现一个有__iter__()和__next__()方法的类&#xff0c;类要能够跟踪内部状态并且在没有元素返回的时候引发StopIteration异常. 这个过程很繁琐而且违反直觉.Generator能够解决这个问题. python generator是一个简单的创建iterator的途径…...

数据结构与算法基础-学习-10-线性表之栈的清理、销毁、压栈、弹栈

一、函数实现1、ClearSqStack&#xff08;1&#xff09;用途清理栈的空间。只需要栈顶指针和栈底指针相等&#xff0c;就说明栈已经清空&#xff0c;后续新入栈的数据可以直接覆盖&#xff0c;不用实际清理数据&#xff0c;提升了清理效率。&#xff08;2&#xff09;源码Statu…...

Leetcode 每日一题 1234. 替换子串得到平衡字符串

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…...

【MYSQL中级篇】数据库数据查询学习

&#x1f341;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; 相关文章 文章名文章地址【MYSQL初级篇】入门…...

华为OD机试真题JAVA实现【火星文计算】真题+解题思路+代码(20222023)

🔥系列专栏 华为OD机试(JAVA)真题目录汇总华为OD机试(Python)真题目录汇总华为OD机试(C++)真题目录汇总华为OD机试(JavaScript)真题目录汇总文章目录 🔥系列专栏题目输入输出描述示例一输入输出说明解题思路核心知识点Code运行结果版...

Linux基础知识

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…...

Linux 游戏性能谁的 更优秀X.Org还是Wayland!

导读X.Org 和 Wayland 是目前 Linux 平台上的两大主流显示服务器&#xff0c;那么两者在 Linux 游戏性能上谁更优秀呢&#xff1f;国外科技媒体 Phoronix 在 Ubuntu 22.10 上对其进行了多款游戏的实测。评测在运行 GNOME 43.1 的 Ubuntu 22.10 上进行测试&#xff0c;在安装英伟…...

【数据结构】算法的复杂度分析:让你拥有未卜先知的能力

&#x1f451;专栏内容&#xff1a;数据结构⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;日拱一卒&#xff0c;功不唐捐 文章目录一、前言二、时间复杂度1、定义2、大O的渐进表示法3、常见的时间复杂度三、空间复杂度1、定义2、常见的空间复杂度一、前…...

Linux根文件系统移植

目录 一、根文件系统 1.1根文件系统 1.2根文件系统内容 二、根文件系统移植 2.1BusyBox 2.2BusyBox的获取 2.3BusyBox的使用 2.4make menuconfig 2.5编译和安装 2.6修改根文件系统 一、根文件系统 1.1根文件系统 根文件系统是内核启动后挂载的第一个文件系统系统引…...

Three.js 无限平面快速教程【Plane】

Three.js 提供了 Plane 概念来表示在 3d 空间中无限延伸的二维表面。 这对于光标交互很有用&#xff0c;因此你可能需要了解如何设置此平面、将其可视化并根据需要进行调整。 推荐&#xff1a;使用 NSDT场景设计器 快速搭建 3D场景。 Three.js 的 Plane 文档很好而且准确&…...

在线预览PDF文件、图片,并且预览地址不显示文件或图片的真实路径。

实现在线预览PDF文件、图片&#xff0c;并且预览地址不显示文件或图片的真实路径。1、vue使用blob流在线预览PDF、图片&#xff08;包括jpg、png等格式&#xff09;。1、按钮的方法&#xff1a;2、方法详细&#xff1a;&#xff08;此方法可以在发起请求时携带token&#xff0c…...

Allegro如何设置导入Subdrawing可自由选择目录操作指导

Allegro如何设置导入Subdrawing可自由选择目录操作指导 用Allgro做PCB设计的时候,导入Subdrawing是非常常用的功能,在导入Subdrawing的时候,通常需要把Subdrawing文件放在需要导入PCB的相同目录下,不能自由选择,如下图 但是Allegro是支持自由选择目录的,只需按照下方的步…...

SpirngMVC执行原理--自学版

DispatcherServlet表示前置控制器&#xff0c;是整个SpringMVC的控制中心&#xff0c;用户发出请求&#xff0c;DispatcherServlet接收请求并拦截请求HandlerMapper为处理器映射。DispatcherServlet调用。HandlerMapping根据请求url查找HandlerHandlerExecution表示具体的Handl…...

获取savemodel的输入输出节点

saved_model_cli show --dir savemodels --all 结果&#xff1a; MetaGraphDef with tag-set: ‘serve’ contains the following SignatureDefs: signature_def[‘translation_signature’]: The given SavedModel SignatureDef contains the following input(s): inputs[‘i…...

《Learning to Reconstruct Botanical Trees from Single Images》学习从单幅图像重建植物树

读书报告下载https://download.csdn.net/download/weixin_43042683/87448211论文原文https://dl.acm.org/doi/10.1145/3478513.3480525论文视频https://www.bilibili.com/video/BV1cb4y127Vp/?fromseopage&vd_source5212838c127b01db69dcc8b2d27ca5171引言植物存在在室外与…...

vant 4 正式发布,支持暗黑主题,那么是如何实现的呢

2022年10月25日首发于掘金&#xff0c;现在同步到公众号。11. 前言大家好&#xff0c;我是若川。我倾力持续组织了一年多源码共读&#xff0c;感兴趣的可以加我微信 lxchuan12 参与。另外&#xff0c;想学源码&#xff0c;极力推荐关注我写的专栏《学习源码整体架构系列》&…...

MySQL的复制 二

复制是MySQL的一项功能&#xff0c;使服务器能够将更改从一个实例恢复到另一个实例 主服务器&#xff08;master&#xff09;将所有数据和结构更改记录到二进制日志中。二进制日志格式是基于语句的、基于行的和混合的。 从属服务器&#xff08;slave&#xff09;从主服务器请求…...

秒杀项目之秒杀商品展示及商品秒杀

目录前言一、登录方式调整二、生成秒杀订单2.1 绑定秒杀商品2.2 查看秒杀商品2.3 订单秒杀2.3.1 移除seata相关&#xff08;方便测压&#xff09;2.3.2 生成秒杀订单2.3.3 前端页面秒杀测试注意前言 博主博客用到的资源都会同步分享到资源包中 一、登录方式调整 第1步&#xf…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...