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]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/237292.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ActiveMQ反序列化RCE漏洞复现(CVE-2023-46604)

漏洞名称 Apache ActiveMQ OpenWire 协议反序列化命令执行漏洞 漏洞描述 Apache ActiveMQ 是美国阿帕奇&#xff08;Apache&#xff09;软件基金会所研发的一套开源的消息中间件&#xff0c;它支持Java消息服务、集群、Spring Framework等。 OpenWire协议在ActiveMQ中被用于…

C语言如何提高程序的可读性?

一、问题 可读性是评价程序质量的一个重要标准&#xff0c;直接影响到程序的修改和后期维护&#xff0c;那么如何提高程序的可读性呢? 二、解答 提高程序可读性可以从以下几方面来进行。 &#xff08;1&#xff09;C程序整体由函数构成的。 程序中&#xff0c;main()就是其中…

ArchVizPRO Interior Vol.8 URP

ArchVizPRO Interior Vol.8 URP是一个在URP中制作的建筑可视化项目。这是一个完全可导航的现代公寓,包括一个带开放式厨房的客厅、休息区、两间卧室和两间浴室。从头开始构建每一个细节,这个室内有130多件家具和道具、自定义着色器和4K纹理。所有家具和道具都非常详细,可以在…

使用 C++/WinRT 创作 API

如果 API 位于 Windows 命名空间中 这是你使用 Windows 运行时 API 最常见的情况。 对于元数据中定义的 Windows 命名空间中的每个类型&#xff0c;C/WinRT 都定义了 C 友好等效项&#xff08;称为投影类型 &#xff09;。 投影类型具有与 Windows 类型相同的完全限定名称&…

【.NET Core】Lazy<T> 实现延迟加载详解

【.NET Core】Lazy 实现延迟加载详解 文章目录 【.NET Core】Lazy<T> 实现延迟加载详解一、概述二、Lazy<T>是什么三、Lazy基本用法3.1 构造时使用默认的初始化方式3.2 构造时使用指定的委托初始化 四、Lazy.Value使用五、Lazy扩展用法5.1 实现延迟属性5.2 Lazy实现…

【LeetCode:30. 串联所有单词的子串 | 滑动窗口 + 哈希表】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

K8S后渗透横向节点与持久化隐蔽方式探索

前言 通常在红蓝对抗中&#xff0c;我们可能会通过各种方法如弱口令、sql注入、web应用漏洞导致的RCE等方法获得服务器的权限&#xff1b;在当前云原生迅猛发展的时代&#xff0c;这台服务器很可能是一个容器&#xff0c;在后续的后渗透由传统的提权变为容器逃逸&#xff0c;内…

【MySQL】导入导出SQL脚本及远程备份---超详细介绍

目录 前言&#xff1a; 一 navcat导入导出 1.1 导入 1.2 导出 二 mysqldump 导入导出 2.1 导入 2.2 导出 三 load data infile命令导入导出 3.1 导入 3.2 导出 四 远程备份 五 思维导图 前言&#xff1a; 随着当今企业发展&#xff0c;数据库的数据越来越多&…

POI:对Excel的基本写操作 整理1

首先导入相关依赖 <!-- https://mvnrepository.com/artifact/org.apache.poi/poi --><!--xls(03)--><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.2</version></depend…

Java副本的概念

在Java中&#xff0c;"副本"&#xff08;copy&#xff09;一词可以用于描述不同的概念&#xff0c;具体取决于上下文。以下是两个常见的用法&#xff1a; 对象的副本&#xff1a;在Java中&#xff0c;当你创建一个对象并将其赋值给另一个变量时&#xff0c;实际上是创…

electron自定义窗口和右键菜单样式

前言 electron默认沿用系统UI&#xff0c;并没有提供很多接口供使用者定制样式&#xff0c;如果想要完全自定义的样式&#xff0c;目前我能想到的方案只能是通过前端自定义样式&#xff0c;然后通过进程通信来实现系统基础功能&#xff1a;最大/小化、关闭、拖动窗口等。 效果…

迈入AI智能时代!ChatGPT国内版免费AI助手工具 peropure·AI正式上线 一个想法写一首歌?这事AI还真能干!

号外&#xff01;前几天推荐的Peropure.Ai迎来升级&#xff0c;现已支持联网模式&#xff0c;回答更新更准&#xff0c;欢迎注册体验&#xff1a; https://sourl.cn/5T74Hu 相信很多人都有过这样的想法&#xff0c;有没有一首歌能表达自己此时此刻的心情&#xff1a; 当你在深…

xtu oj 1329 连分式

题目描述 连分式是形如下面的分式&#xff0c;已知a,b和迭代的次数n&#xff0c;求连分式的值。 输入 第一行是一个整数T(1≤T≤1000)&#xff0c;表示样例的个数。 每行一个样例&#xff0c;为a,b,n(1≤a,b,n≤9) 输出 每行输出一个样例的结果&#xff0c;使用x/y分式表达…

Go-安装与基础语法

TOC 1. Go 安装与环境变量 1.1 下载 需要从Go语言的官方网站下载适合你操作系统的Go语言安装包。Go语言支持多种操作系统&#xff0c;包括Windows、Linux和Mac OS。 对于Windows用户&#xff0c;下载.msi文件&#xff0c;然后双击该文件&#xff0c;按照提示进行安装即可。…

学习Qt笔记

前言&#xff1a; 学习笔记的内容来自B站up主阿西拜编程 《Qt6 C开发指南 》2023&#xff08;上册&#xff0c;完整版&#xff09;_哔哩哔哩_bilibili《Qt6 C开发指南 》2023&#xff08;上册&#xff0c;完整版&#xff09;共计84条视频&#xff0c;包括&#xff1a;00书籍介…

FreeRTOS系统配置

一、前言 在实际使用FreeRTOS 的时候我们时常需要根据自己需求来配置FreeRTOS&#xff0c;而且不同架构 的MCU在使用的时候配置也不同。FreeRTOS的系统配置文件为FreeRTOSConfig.h&#xff0c;在此配置文件中可以完成FreeRTOS的裁剪和配置&#xff0c;这是非常重要的一个文件&a…

git修改历史(非最新)提交信息

二、修改最近第二次或更早之前的commit信息 当前有三次提交&#xff0c;从近到远分别为1、2、3 以修改第2次提交为例&#xff08;从最新往前数&#xff09; 1、使用命令git rebase -i HEAD~2 按i进入编辑模式&#xff0c;将对应的pick改为edit&#xff0c;然后ctrlc退出。最…

C++学习笔记(二十八):c++ 静态库及动态库的使用

静态库的使用 库的使用会很大程度减少我们的工作&#xff0c;本节对c中静态库和动态库的使用进行简单的介绍。静态链接库意味着这个库会被放到可执行文件中&#xff0c;在生成的exe中。动态链接库是在程序运行时链接的&#xff0c;可以在程序运行时调用加载库函数的方法来实现&…

【Linux运维】LVM和RAID学习及实践

LVM和RAID学习及实践 背景LVM简介新加硬盘的操作RAID-磁盘阵列应用场景RAID0RAID1其他结构RAID制作RAID 小结 背景 某台服务器的磁盘管理需要自己动手处理&#xff0c;找了一些资料也踩了一些坑&#xff0c;在这里记录一下&#xff0c;先介绍一下LVM和RAID这两个东西。在计算机…

通过反射修改MultipartFile类文件名

1、背景 项目上有这样一个需求&#xff0c;前端传文件过来&#xff0c;后端接收后按照特定格式对文件进行重命名。(修改文件名需求其实也可以在前端处理的) //接口类似于下面这个样子 PosMapping("/uploadFile") public R uploadFile(List<MultipartFile> fil…