陷入闭包:理解 React 状态管理中的怪癖

在这里插入图片描述
TLDR

  • 闭包就像函数随身携带的背包,包含它们创建时的数据
  • React 组件使用闭包来记住它们的状态和属性
  • 过时的闭包可能导致状态更新不如预期时的错误
  • 函数式更新提供了一个可靠的方式来处理最新状态

简介
你是否曾经疑惑过,为什么有时你的 React 状态更新不太对劲?或者为什么快速多次点击按钮并没有如预期地更新计数器?答案在于理解闭包以及 React 如何处理状态更新。在这篇文章中,我们将通过简单的例子来解开这些概念,让一切变得清晰。

什么是闭包?
可以把闭包想象成一个函数,它保留了一丝它出生地的记忆。它就像一张所有在函数创建时存在的变量的即时照片。让我们通过一个简单的计数器来看这个概念的实际应用:

function createPhotoAlbum() {
let photoCount = 0; // 这是我们“快照”变量
functionaddPhoto() {photoCount += 1; // 这个函数“记得”photoCountconsole.log(`相册中的照片: ${photoCount}`);}
functiongetPhotoCount() {console.log(`当前照片数: ${photoCount}`);}
return { addPhoto, getPhotoCount };
}const myAlbum = createPhotoAlbum();
myAlbum.addPhoto(); // "相册中的照片: 1"
myAlbum.addPhoto(); // "相册中的照片: 2"
myAlbum.getPhotoCount(); // "当前照片数: 2"

在这个例子中,addPhoto 和 getPhotoCount 函数都记得 photoCount 变量,即使 createPhotoAlbum 已经执行完毕。这就是闭包的作用——函数记得它们的出生地!

为什么闭包在 React 中很重要
在 React 中,闭包在组件如何记住它们的状态方面扮演着关键角色。这里有一个简单的计数器组件:

function Counter() {
const [count, setCount] = useState(0);
constincrement = () => {// 这个函数对 'count' 形成闭包setCount(count + 1);};
return (<>Count: {count}<button onClick={increment}>Add One</button></>);
}
increment 

increment 函数围绕 count 状态变量形成了一个闭包。这就是它“记得”按钮点击时应该增加哪个数字的方式。

问题:过时的闭包
这里事情变得有趣了。让我们创建一个可能导致意外行为的场景:

function BuggyCounter() {
const [count, setCount] = useState(0);
constincrementThreeTimes = () => {// 所有这些更新看到的都是同一个 'count' 值!setCount(count + 1); // count 是 0setCount(count + 1); // count 仍然是 0setCount(count + 1); // count 仍然是 0!};
return (<>Count: {count}<button onClick={incrementThreeTimes}>Add Three</button></>);
}

如果你点击这个按钮,你可能会期望计数增加 3。但惊喜!它只增加了 1。这是因为“过时的闭包”——我们的函数被困在查看它创建时的原始 count 值。这就像拍了三张显示数字 0 的白板的照片,然后试图给每张照片加 1。每张照片上仍然会是 0!

解决方案:函数式更新

React 提供了一个优雅的解决方案来解决这个问题——函数式更新:

function FixedCounter() {
const [count, setCount] = useState(0);
constincrementThreeTimes = () => {// 每次更新都建立在之前的基础上setCount(current => current + 1); // 0 -> 1setCount(current => current + 1); // 1 -> 2setCount(current => current + 1); // 2 -> 3};
return (<>Count: {count}<button onClick={incrementThreeTimes}>Add Three</button></>);
}

我们不再使用闭包中的值,而是告诉 React“取当前的值并加 1”。这就像有一个总是先查看白板上当前数字再进行添加的有帮助的助手!

真实世界示例:社交媒体点赞按钮
让我们看看这在真实世界场景中的应用——社交媒体帖子的点赞按钮:

function SocialMediaPost() {
const [likes, setLikes] = useState(0);
const [isProcessing, setIsProcessing] = useState(false);// 有问题的版本 - 可能错过快速点击
consthandleLikeBuggy = async () => {if (isProcessing) return;setIsProcessing(true);awaitupdateLikeInDatabase(likes + 1); // 使用过时的 'likes' 值setLikes(likes + 1);setIsProcessing(false);};// 修复版本 - 捕获所有点击
consthandleLikeFixed = async () => {if (isProcessing) return;setIsProcessing(true);// 使用函数式更新确保我们有最新的计数setLikes(currentLikes => {updateLikeInDatabase(currentLikes + 1);return currentLikes + 1;});setIsProcessing(false);};return (<button onClick={handleLikeFixed}>Like Post ({likes})</button>);
}

结论

关键要点

  1. 闭包是记得它们创建时变量的函数——就像有记忆的函数。
  2. 过时的闭包发生在你的函数使用其记忆中的过时值而不是当前值时。
  3. 函数式更新在 React 中(setCount(count => count + 1))确保你总是使用最新的状态。

记住:当基于前一个值更新状态时,优先使用函数式更新。这就像有一个可靠的助手,总是在做更改前检查当前值,而不是依赖记忆!

最佳实践

  • 当新状态依赖于前一个状态时,使用函数式更新
  • 在异步操作和事件处理中特别小心闭包
  • 有疑问时,使用 console.log 检查过时的闭包
  • 考虑使用 React DevTools 调试状态更新

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

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

相关文章

Cloud Computing-HCIA

FusionAccess 快速封装虚拟机特点 拥有独立的存储空间系统盘。方便统一更新和还原。同一模板分发的话 SID 会相同。 在虚拟机桌面发放流程中&#xff0c;创建虚拟机时&#xff0c;HDC 组件会检查指定的计算机组和桌面组。 KVM Kernel-based Virtual Machine&#xff0c;即基于…

LeetCode 3442.奇偶频次间的最大差值 I

给你一个由小写英文字母组成的字符串 s 。请你找出字符串中两个字符的出现频次之间的 最大 差值&#xff0c;这两个字符需要满足&#xff1a; 一个字符在字符串中出现 偶数次 。 另一个字符在字符串中出现 奇数次 。 返回 最大 差值&#xff0c;计算方法是出现 奇数次 字符的次…

基于STM32的智能加湿器设计(新版本)

目录 1、设计要求 2、系统功能 3、演示视频和实物 4、系统设计框图 5、软件设计流程图 6、原理图 7、主程序 8、总结 &#x1f91e;大家好&#xff0c;这里是5132单片机毕设设计项目分享&#xff0c;今天给大家分享的是加湿器。设备的详细功能见网盘中的文章《12、基于…

21.2.2 保存

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 如果打开某个Excel文件修改后&#xff0c;需要保存到原文件或者用新的文件名保存&#xff0c;在 Excel.Application.Quit() 前使用W…

华水967数据结构2024真题(回忆版)

一、 选择[10道) (20分). 1、数据结构中&#xff0c;从逻辑结构上可以把数据结构分为() 答案&#xff1a;线性结构和非线性结构 2、给了一个二叉树的中序遍历&#xff0c;求二叉树的后序遍历 解析&#xff1a; 要根据中序遍历的结果来推导后序遍历&#xff0c;需要知道二叉…

除了网页,还有哪些方式可以访问deepseek r1

DeepSeek R1 是一款先进的人工智能模型&#xff0c;用户可以通过多种方式进行访问和使用&#xff1a; 官方网站&#xff1a;用户可以直接访问 DeepSeek 的官方网站&#xff0c;注册账户后即可使用其在线服务。 API 集成&#xff1a;开发者可以通过 DeepSeek 提供的 API&#x…

Deepseek v3R1 学习笔记

o1 o1 模型在训练过程中混合了多种奖励函数的设计方法&#xff0c;并且尝试从结果监督转向过程监督&#xff0c;在中间过程进行打分 使用的搜索策略&#xff1a;基于树的搜索和基于顺序修改的搜索 R1 R1-Zero 是从基础模型开始&#xff0c;完全由强化学习驱动&#xff0c;不…

Linux特权组全解析:识别GID带来的权限提升风险

组ID&#xff08;Group ID&#xff0c;简称 GID&#xff09;是Linux系统中用来标识不同用户组的唯一数字标识符。每个用户组都有一个对应的 GID&#xff0c;通过 GID&#xff0c;系统能够区分并管理不同的用户组。 在Linux系统中&#xff0c;系统用户和组的配置文件通常包括以…

时间对象管理相关

在SpringBoot项目中&#xff0c;这段时间感觉对于时间的处理是个比较繁琐的问题&#xff0c;现将一些常用的时间处理记录下。 一般来说&#xff0c;在MySQL数据库中时间字段选择DateTime&#xff0c;java中实体类时间字段使用Date类型&#xff0c;为了转换方便&#xff0c;使用…

mybatisgenerator接入

1、引入plugin <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.mybatis.generator</groupId>&l…

NacosRce到docker逃逸实战

NacosRce到docker逃逸实战 1、Nacos Derby Rce打入内存马 这个漏洞的原理大家应该都知道&#xff0c; 2.3.2 < Nacos < 2.4.0版本默认derby接口未授权访问&#xff0c;攻击者可利用未授权访问执行SQL语句加载构造恶意的JAR包导致出现远程代码执行漏洞。 在日常的漏洞挖…

SpringCloud速通教程

视频地址 文档地址 3. SpringCloud - 快速通关

mini-lsm通关笔记Week2Day6

项目地址&#xff1a;https://github.com/skyzh/mini-lsm 个人实现地址&#xff1a;https://gitee.com/cnyuyang/mini-lsm Summary 在本章中&#xff0c;您将&#xff1a; 实现WAL日志文件的编解码 系统重启时使用WAL日志恢复memtable 要将测试用例复制到启动器代码中并运行…

《手札·开源篇》基于开源Odoo软件与Deepseek的智能企业管理系统集成方案

一、方案背景 随着企业数字化转型的深入&#xff0c;传统ERP系统需要结合AI技术实现智能化升级。本方案将开源ERP系统Odoo与深度求索&#xff08;Deepseek&#xff09;大模型能力深度整合&#xff0c;构建具备智能决策支持、自然语言交互和数据分析增强的企业管理平台。 二、…

【DeepSeek背后的技术】系列三:强化学习(Reinforcement Learning, RL)

目录 1 简介1.1 强化学习&#xff08;RL&#xff09;简介1.2 基于人类反馈的强化学习 (RLHF) 简介1.2.1 四个重要角色1.2.2 三个步骤 2 强化算法2.1 PPO&#xff08;Proximal Policy Optimization&#xff09;2.1.1 核心思想2.1.2 算法步骤2.1.3 优点2.1.4 缺点2.1.5 应用场景 …

LabVIEW的智能电源远程监控系统开发

在工业自动化与测试领域&#xff0c;电源设备的精准控制与远程管理是保障系统稳定运行的核心需求。传统电源管理依赖本地手动操作&#xff0c;存在响应滞后、参数调节效率低、无法实时监控等问题。通过集成工业物联网&#xff08;IIoT&#xff09;技术&#xff0c;实现电源设备…

SpringBoot开发(四)SpringBoot配置文件

1. SpringBoot配置文件 1.1. 配置端口号和路径 &#xff08;1&#xff09;在application.properties文件下配置端口号和路径。 server.port: 8081 server.servlet.context-path/demo&#xff08;2&#xff09;运行访问。 1.2. 自定义配置 1.2.1. 方式一 &#xff08;1&…

算法随笔_40: 爬楼梯

上一篇:算法随笔_39: 最多能完成排序的块_方法2-CSDN博客 题目描述如下: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&am…

C# List 列表综合运用实例⁓Hypak原始数据处理编程小结

C# List 列表综合运用实例⁓Hypak原始数据处理编程小结 1、一个数组解决很麻烦引出的问题1.1、RAW 文件尾部数据如下:1.2、自定义标头 ADD 或 DEL 的数据结构如下&#xff1a; 2、程序 C# 源代码的编写和剖析2.1、使用 ref 关键字&#xff0c;通过引用将参数传递&#xff0c;以…

win32汇编环境,窗口程序中自定义工具栏的使用示例

;运行效果 ;win32汇编环境,窗口程序中自定义工具栏的使用示例 ;工具栏一般放在菜单下面&#xff0c;相当于一个个小的对话框&#xff0c;当然你放在其它地方也可以。 ;原理是&#xff0c;创建一张BMP位图&#xff0c;比如下例用一张168*24的图&#xff0c;平均分成7部分&#x…