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

React Hooks中useState的介绍,并封装为useSetState函数的使用

useState 允许我们定义状态变量,并确保当这些状态变量的值发生变化时,页面会重新渲染。

useState 返回值

const [state, setState] = useState(initialState);

useState 返回一个长度为 2 的数组。通常,我们这样定义状态变量:

const [key, setKey] = useState(0);

但实际上,我们也可以这样写:

const keyArr = useState(0);
const key = keyArr[0];
const setKey = keyArr[1];

这种写法显得有些繁琐,但它有助于我们理解 useState 的返回值类型。

useState 定义初始值

useState 定义初始值有两种用法:

  • 传入一个初始值
  • 传入一个函数

传入一个初始值

const [key, setKey] = useState(0);

传入一个函数

const [key, setKey] = useState(() => {
return 0;
});

useState 更新状态

更新状态有两种用法:

import { useState } from "react";
import { Card, Button, Space } from "antd";const Counter = () => {const [count, setCount] = useState(0);// 方法一function handleClick() {setCount(count + 1);}// 方法二function handleClickFn() {setCount((prevCount) => {return prevCount + 1;});}return (<Card><Space><div>Count: {count}</div><Button onClick={handleClick} type="primary">count+1[方法一]</Button><Button onClick={handleClickFn} type="primary">count+1[方法二]</Button><Button onClick={() => setCount(0)} type="primary">重置</Button></Space></Card>);
};export default Counter;

这两种更新状态值的方式都是用于更新 useState 中的状态,但它们在某些情况下的行为是不同的。以下是它们之间的主要区别:

直接传递新的状态值:

在 handleClick 函数中,我们直接传递了一个新的状态值给 setCount 函数:

function handleClick() {
setCount(count + 1);
}

这种方法是基于当前渲染周期中的 count 值。如果 setCount 被连续调用多次,React 可能会批量处理这些更新,导致不预期的结果。

传递一个函数来更新状态:

在 handleClickFn 函数中,我们传递了一个函数给 setCount

function handleClickFn() {
setCount((prevCount) => {
return prevCount + 1;
});
}

这种方法是基于先前的状态值。这个函数接收先前的状态值作为参数,并返回一个新的状态值。由于它总是基于最新的状态值,所以即使 setCount 被连续调用多次,也能得到预期的结果。

举例说明:

import { useState } from "react";
import { Card, Button, Space } from "antd";const Counter = () => {const [count, setCount] = useState(0);// 方法一function handleMultipleUpdates() {setCount(count + 1);setCount(count + 1);setCount(count + 1);}// 方法二function handleMultipleUpdatesFn() {setCount((prevCount) => prevCount + 1);setCount((prevCount) => prevCount + 1);setCount((prevCount) => prevCount + 1);}return (<Card><Space><div>Count: {count}</div><Button onClick={handleMultipleUpdates} type="primary">批量更新[方法一]</Button><Button onClick={handleMultipleUpdatesFn} type="primary">批量更新[方法二]</Button><Button onClick={() => setCount(0)} type="primary">重置</Button></Space></Card>);
};export default Counter;

考虑以下代码:

function handleMultipleUpdates() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}

如果 count 的初始值为 0,调用 handleMultipleUpdates 函数后,你可能期望 count 的值为 3。但实际上,由于三次 setCount 调用都基于同一个 count 值,结果 count 的值仍然为 1。

相反,如果我们使用函数式更新:

function handleMultipleUpdatesFn() {
setCount((prevCount) => prevCount + 1);
setCount((prevCount) => prevCount + 1);
setCount((prevCount) => prevCount + 1);
}

这时,每次 setCount 调用都基于最新的状态值,所以 count 的值会如预期地增加到 3。

结论:

  • 当状态更新不依赖于先前的状态值时,可以直接传递一个新的状态值。
  • 当状态更新依赖于先前的状态值,或者你需要连续多次更新状态时,建议使用函数式更新。

useState 的惰性初始化

useState 的初始状态参数支持惰性初始化。这意味着你可以传递一个函数作为 useState 的参数,这个函数会在组件的首次渲染时被调用,而不是在每次渲染时都被调用。

惰性初始化的主要用途是为了优化性能。当初始化状态需要进行计算密集型操作或其他昂贵的操作时,你不希望这些操作在每次组件渲染时都被执行。通过使用惰性初始化,你可以确保这些操作只在组件首次渲染时执行一次。

假设你有一个大的 JSON 数据,你只想在组件首次渲染时解析它:

const bigJsonData = "{...}"; // 大量的 JSON 数据
function MyComponent() {
const [data, setData] = useState(() => {
console.log("Parsing JSON");
return JSON.parse(bigJsonData);
});
// ... 其他代码
}

在上述代码中,console.log("Parsing JSON") 只会在 MyComponent 首次渲染时打印一次,即使组件重新渲染多次。这是因为 useState 使用了惰性初始化,所以传递给它的函数只在首次渲染时被调用。

这种特性在处理大量数据或昂贵的计算时特别有用,因为它可以避免不必要的重复操作,从而提高应用的性能。

useState 是异步函数吗

useState 不是异步函数。

在 React 中,当你调用 setState 或 useState 时,React 并不会立即更新组件。相反,它会将更新标记为“待处理”,并将其添加到一个更新队列中。然后,React 的调度机制会决定何时进行这些更新。

在 React 18 中,引入了新的并发模式,这使得 React 的调度机制变得更加复杂。在并发模式下,React 可以在多个更新之间进行切换,这意味着在某些情况下,useState 的行为可能看起来像是异步的。但实际上,这是由于 React 的调度机制,而不是 useState 本身是异步的。

例如,考虑以下代码:

function MyComponent() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count);
};
return <button onClick={handleClick}>Click me</button>;
}

在这个例子中,当你点击按钮时,你可能期望控制台会打印出更新后的计数值。但实际上,它会打印出点击按钮时的计数值。这是因为 setCount 并不会立即更新 count 的值,而是将更新排入队列,等待 React 的调度机制决定何时进行更新。

因此,虽然 useState 可能在某些情况下表现得像是异步的,但这实际上是由于 React 的调度机制,而不是 useState 本身是异步的。

利用 useState 封装自定义 Hook-useSetState

import { useCallback, useState } from "react";
/*** 一个自定义 hook,提供 setState 功能,但与 class 组件中的 setState 类似,* 它允许合并状态更新,而不是替换它。** @param {Object} initialState - 初始状态,默认为空对象。* @returns {Array} 返回一个数组,第一个元素是当前状态,第二个元素是合并状态的函数。*/
const useSetState = (initialState = {}) => {
// 使用 useState hook 设置初始状态
const [state, setState] = useState(initialState);
// 定义一个合并状态的函数
const setMergeState = useCallback((patch) => {
setState((prevState) => ({
...prevState, // 保留之前的状态
// 如果 patch 是一个函数,那么使用该函数返回的结果来更新状态,
// 否则直接使用 patch 对象来更新状态。
...(typeof patch === "function" ? patch(prevState) : patch),
}));
}, []); // 使用空依赖数组,确保该回调函数不会重新创建
// 返回当前状态和合并状态的函数
return [state, setMergeState];
};
// 导出自定义 hook
export default useSetState;

这个自定义 Hook useSetState 的好处主要有以下几点:

  1. 状态合并:与类组件中的 setState 方法类似,useSetState 允许你合并状态,而不是完全替换它。这在处理复杂状态对象时特别有用,因为你可以只更新状态对象的某一部分,而不是每次都提供完整的新状态。

  2. 简化状态更新:在使用原生的 useState Hook 时,如果你想合并状态,你需要手动做这个合并。而 useSetState 提供了一个更简洁的方法来实现这一点,使得代码更加整洁和易读。

  3. 函数式更新useSetState 支持传递一个函数作为参数,这个函数接受当前状态并返回要合并的新状态。这允许你基于当前状态来计算新的状态,这在处理依赖于当前状态的更新时非常有用。

  4. 更接近类组件的体验:对于那些习惯于类组件的开发者来说,useSetState 提供了一个更熟悉的 API 来处理状态,这可能会使从类组件迁移到函数组件的过渡更加顺畅。

总的来说,useSetState 提供了一个更加灵活和强大的方法来处理组件状态,特别是当你需要合并状态或基于当前状态来计算新的状态时。

调用 useState 后大致执行情况

graph TD
A[调用useState] -->|useState返回一个状态变量和一个更新函数| B[useState返回]
B -->|当你调用更新函数时,React会将新的状态添加到一个更新队列中| C[调用更新函数]
C -->|React的调度机制会决定何时进行更新| D[scheduleUpdateOnFiber]
D -->|确保根节点被正确地调度| E[ensureRootIsScheduled]
E -->|处理同步工作循环| F[workLoopSync]
F -->|开始新的渲染| G[performSyncWorkOnRoot]
G -->|开始处理当前的fiber| H[beginWork]
H -->|处理hooks并返回新的React元素| I[renderWithHooks]
I -->|比较新旧React元素并确定是否需要更新DOM| J[reconcileChildren]
J -->|处理提交前的副作用| K[commitBeforeMutationEffects]
K -->|将新的React元素提交到DOM| L[commitRoot]
L -->|完成更新| M[更新DOM]

相关文章:

React Hooks中useState的介绍,并封装为useSetState函数的使用

useState 允许我们定义状态变量&#xff0c;并确保当这些状态变量的值发生变化时&#xff0c;页面会重新渲染。 useState 返回值 const [state, setState] useState(initialState);useState 返回一个长度为 2 的数组。通常&#xff0c;我们这样定义状态变量&#xff1a; co…...

5 个最适合SEI 网络空投交易等操作的钱包(Bitget Wallet,Coin98等)

大家好&#xff01;Sei 网络比 SOL 快 5 倍&#xff0c;手续费低&#xff0c;还能防止前台交易。好了&#xff0c;我不会占用大家太多时间&#xff0c;让我们直奔主题吧。 Sei 官方&#xff1a;推特&#xff08;twitter.com/SeiNetwork&#xff09; 如上图所示&#xff0c;目前…...

.net8 AOT编绎-跨平台调用C#类库的新方法-函数导出

VB.NET AOT无法编绎DLL,微软的无能&#xff0c;正是你的机会 .net8 AOT编绎-跨平台调用C#类库的新方法-函数导出 1&#xff0c;C#命令行创建工程&#xff1a;dotnet new classlib -o CSharpDllExport 2&#xff0c;编写一个静态方法&#xff0c;并且为它打上UnmanagedCallersO…...

第三十八周周报:文献阅读 +BILSTM+GRU+Seq2seq

目录 摘要 Abstract 文献阅读&#xff1a;耦合时间和非时间序列模型模拟城市洪涝区洪水深度 现有问题 提出方法 创新点 XGBoost和LSTM耦合模型 XGBoost算法 ​编辑 LSTM&#xff08;长短期记忆网络&#xff09; 耦合模型 研究实验 数据集 评估指标 研究目的 洪…...

天津最新web前端培训班 如何提升web技能?

随着互联网的迅猛发展&#xff0c;web前端成为了一个热门的职业方向。越来越多的人希望能够通过学习web前端技术来提升自己的就业竞争力。为了满足市场的需求&#xff0c;许多培训机构纷纷推出了web前端培训课程。 什么是WEB前端 web前端就是web给用户展示的东西&#xff0c;…...

Linux下QT生成的(.o)、(.a)、(.so)、(.so.1)、(.so.1.0)、(.so.1.0.0)之间的区别

记录一下遇到的问题&#xff1a;Linux系统下Qt编译第三方动态库会生成多个.so文件&#xff0c;不了解的小伙伴可能很疑惑&#xff1a; &#xff08;1&#xff09;Linux 下 QT 生成的&#xff08;.o&#xff09;、&#xff08;.a&#xff09;和&#xff08;.so&#xff09;三个文…...

线性代数 --- 为什么LU分解中L矩阵的行列式一定等于正负1?

以下是关于下三角矩阵L的行列式一定等于-1的一些说明 笔者的一些话(写在最前面)&#xff1a; 这是一篇小文&#xff0c;是我写的关于求解矩阵行列式的一篇文章中的一部分。之所以把这一段专门提溜出来&#xff0c;是因为这一段相对于原文是可以完全独立的&#xff0c;也是因为我…...

Redisson 源码解析 - 分布式锁实现过程

一、Redisson 分布式锁源码解析 Redisson是架设在Redis基础上的一个Java驻内存数据网格。在基于NIO的Netty框架上&#xff0c;充分的利用了Redis键值数据库提供的一系列优势&#xff0c;在Java实用工具包中常用接口的基础上&#xff0c;为使用者提供了一系列具有分布式特性的常…...

玩转贝启科技BQ3588C开源鸿蒙系统开发板 —— 开发板详情与规格

本文主要参考&#xff1a; BQ3588C_开发板详情-开源鸿蒙技术交流-Bearkey-开源社区 BQ3588C_开发板规格-开源鸿蒙技术交流-Bearkey-开源社区 厦门贝启科技有限公司-Bearkey-官网 1. 开发板详情 RK3588 核心板是一款由贝启科技自主研发的基于瑞芯微 RK3588 AI 芯片的智能核心…...

Qt pro文件

1. 项目通常结构 2.pri文件 pri文件可定义通用的宏&#xff0c;例如创建一个COMMON.pri文件内容为 COMMON_PATH D:\MyData 然后其它pri或者pro文件如APPTemplate.pro文件中通过添加include(Common.pri) &#xff0c;QtCreator就会自动在项目结构树里面创建对应的节点 3.变量…...

实验笔记之——服务器链接

最近需要做NeRF相关的开发,需要用到GPU,本博文记录本人配置服务器远程链接的过程,本博文仅供本人学习记录用~ 连上服务器 首先先确保环境是HKU的网络环境(HKU AnyConnect也可)。伙伴已经帮忙创建好用户(第一次登录会提示重新设置密码)。用cmd ssh链接ssh -p 60001 <u…...

微服务-java spi 与 dubbo spi

Java SPI 通过一个案例来看SPI public interface DemoSPI {void echo(); } public class FirstImpl implements DemoSPI{Overridepublic void echo() {System.out.println("first echo");} } public class SecondImpl implements DemoSPI{Overridepublic void ech…...

redis复习笔记03(小滴课堂)

Redis6常见数据结构概览 0代表存在&#xff0c;1代表不存在。 1表示删除成功&#xff0c;0表示失败。 查看类型&#xff0c;默认string类型。 也可以设置set类型。 list类型。 查看key的过期时间&#xff1a; Redis6数据结构之String类型介绍和应用场景 批量设置&#xff1a; …...

【Spring Cloud】关于Nacos配置管理的详解介绍

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Spring Cloud》。&#x1f3af;&#x1f3af; &am…...

基于Java SSM框架实现校园网络维修系统项目【项目源码】

基于java的SSM框架实现校园网络维修系统演示 java简介 Java主要采用CORBA技术和安全模型&#xff0c;可以在互联网应用的数据保护。它还提供了对EJB&#xff08;Enterprise JavaBeans&#xff09;的全面支持&#xff0c;java servlet API&#xff0c;JSP&#xff08;java serve…...

项目框架构建之3:Nuget服务器的搭建

本文是“项目框架构建”系列之3&#xff0c;本文介绍一下Nuget服务器的搭建&#xff0c;这是一项简单的工作&#xff0c;您或许早已会了。 1.打开vs2022创建Asp.net Web应用程序 框架选择.net framework4.8&#xff0c;因为nuget服务器只支持.net framework。 2.选择空项目和保…...

外包干了1个月,技术退步一大半。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入广州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…...

167. 木棒(dfs剪枝,经典题)

167. 木棒 - AcWing题库 乔治拿来一组等长的木棒&#xff0c;将它们随机地砍断&#xff0c;使得每一节木棍的长度都不超过 50 个长度单位。 然后他又想把这些木棍恢复到为裁截前的状态&#xff0c;但忘记了初始时有多少木棒以及木棒的初始长度。 请你设计一个程序&#xff0…...

用HTML的原生语法实现两个div子元素在同一行中排列

代码如下&#xff1a; <div id"level1" style"display: flex;"><div id"level2-1" style"display: inline-block; padding: 10px; border: 1px solid #ccc; margin: 5px;">这是第一个元素。</div><div id"…...

C++进阶--map和set的介绍及使用

map和set的介绍及使用 一、关联式容器与键值对关联式容器键值对pair树形结构的关联式容器 二、set2.1 set的介绍2.2 set的使用2.2.1 set的模板参数列表2.2.2 set的构造2.2.3 set的迭代器2.2.4 set的容量2.2.5 set修改操作2.2.6 set的使用举例 三、multiset3.1 multiset的介绍3.…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

MFC内存泄露

1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...