news 2026/4/8 21:31:31

React 闭包陷阱:一个空依赖数组,毁了我的数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
React 闭包陷阱:一个空依赖数组,毁了我的数据


前天晚上,我正在给自己的开源项目 SkillLauncher Windows 版本收尾。

这是一个帮助开发者快速启动 Claude Code Skills 的桌面工具。功能很简单:点击某个技能卡片,工具就会自动把技能名字复制到剪贴板,同时记录下你使用了哪些技能、用了多少次。

测试的时候一切正常。我用了一次「commit」,再刷新页面,使用记录还在。我又用了「pdf」,刷新,也还在。

完美打包,准备发版。

第二天早上打开一测——

所有的使用记录都不见了。

空空如也的列表盯着我的脸,那一刻我甚至怀疑自己是不是做梦记错了。


问题一:消失的数据

我打开开发者工具,定位到数据文件:C:\Users\admin\AppData\Local\com.skillLauncher.app\skill-usage.json

文件存在,但内容是空的。

为什么会这样?让我带你看下原来的代码:

setUsageData((currentData)=>{constnewData={usage:newUsage};// 异步保存(async()=>{awaitwriteFile(filePath,encoder.encode(JSON.stringify(newData)));})();returnnewData;});

发现问题了吗?

直接覆盖写入,没有考虑文件中已有的数据。

但这还不是最致命的。更严重的问题在于加载逻辑:

asyncfunctionloadUsageData(){try{constdata=JSON.parse(jsonStr);setUsageData(data);}catch(err){// 任何读取失败都会导致空数据setUsageData({usage:[]});// 危险!}}

这是一个时序炸弹

  1. loadUsageData读取失败(可能是临时权限问题、文件被占用、JSON 解析错误)
  2. 内存状态被设为{ usage: [] }
  3. 用户点击技能,触发保存
  4. 保存逻辑基于空数据计算,然后用空数据覆盖文件
  5. 文件中的有效数据,永久丢失

教训:永远不要用空数据去覆盖可能有数据的文件。

解决方案:合并写入

修复后的代码遵循一个原则:先读后写,合并而非覆盖

// 保存前先读取现有文件,合并后再写入try{const{readFile}=awaitimport("@tauri-apps/plugin-fs");constexistingContents=awaitreadFile(filePath);constexistingData=JSON.parse(decoder.decode(existingContents));if(existingData?.usage?.length>0){// 使用 Map 合并数据(新记录覆盖旧的同名记录)constmergedMap=newMap<string,SkillUsageRecord>();// 先添加现有记录existingData.usage.forEach(record=>{if(record?.name){mergedMap.set(record.name,record);}});// 再添加新记录(自动覆盖同名的)newUsage.forEach(record=>{if(record?.name){mergedMap.set(record.name,record);}});constmergedData={usage:Array.from(mergedMap.values())};awaitwriteFile(filePath,encoder.encode(JSON.stringify(mergedData,null,2)));return;// 合并成功,直接返回}}catch(readErr){// 文件不存在或读取失败,创建新文件}// 正常保存新文件awaitwriteFile(filePath,encoder.encode(jsonStr));

核心思想很简单:保存前先读出文件里的旧数据,和内存中的新数据合并,然后再写回去。这样即使加载时出了问题,文件里的数据也不会丢。


问题二:永远走不进去的 if

修复完数据问题,我以为可以收工了。

结果又发现一个 bug:我的「等待加载完成」逻辑从来没生效过。

const[loadCompleted,setLoadCompleted]=useState(false);constrecordUsage=useCallback(async(skillName:string)=>{// 检查加载状态if(!loadCompleted){console.log("等待加载完成...");// 等待逻辑}// ...},[]);// 空依赖数组!

这段代码看起来没问题吧?

但实际运行时,loadCompleted永远是false。即使我调用了setLoadCompleted(true)recordUsage函数里读到的还是false

这就是经典的React 闭包陷阱

为什么会这样?

useCallback的依赖数组为空时,回调函数只在组件挂载时创建一次:

// 组件挂载时,loadCompleted = falseconstrecordUsage=useCallback(()=>{console.log(loadCompleted);// 闭包捕获了 false},[]);// 空依赖,永不更新// 即使后来 setLoadCompleted(true)// recordUsage 内部的 loadCompleted 仍然是 false

JavaScript 闭包捕获的是变量值而非变量引用。这就像你拍了一张照片,之后无论被拍摄的人怎么换衣服,照片里的样子永远不会变。

解决方案:useRef

最简单的修复方式是用useRef

// useRef 返回的对象在整个组件生命周期内保持不变constloadCompletedRef=useRef(false);// 修改值通过 .currentloadCompletedRef.current=true;// useCallback 内部访问 .currentconstrecordUsage=useCallback(async(skillName:string)=>{if(!loadCompletedRef.current){// 总是获取最新值// 等待逻辑}},[]);

useRef返回的是一个可变对象{ current: ... }。对象的引用在闭包中保持不变,但通过.current访问的始终是最新值。

这就像拍了一张视频而不是照片,内容会实时更新。


总结

这两个问题花了我大半夜时间,但也总结出一些经验:

数据持久化最佳实践

  1. 先读后写:保存前读取现有数据,合并后再写入
  2. 防御性检查:验证数据格式,避免用空数据覆盖
  3. 错误恢复:写入失败时保留原有数据

React Hooks 避坑指南

场景问题解决方案
useCallback中变量值过期空依赖数组导致闭包捕获初始值使用useRef
setInterval中 state 过期定时器回调捕获初始 state使用 useRef
事件监听器中 state 过期监听器闭包捕获旧值每次更新时重新添加监听器

其实这些都不是什么高深的技巧,只是在写代码时多想一步:如果这里出错了,会发生什么?

开源项目 SkillLauncher 已经发布,欢迎体验:

https://github.com/gxj1134506645/skillLauncher-windows


欢迎关注公众号 FishTech Notes,一块交流使用心得!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/3 4:28:21

16位汇编常见指令

16位汇编常见指令 1)16位汇编 16位汇编基于8086/8088处理器架构,是DOS系统下的核心汇编语言,核心指令按功能分类如下: 数据传输类:MOV(数据复制),LEA(取内存偏移地址),PUSH(压栈),POP(出栈),XCHG(交换操作数); 算术运算类:ADD(加法),SUB(减法),MUL(…

作者头像 李华
网站建设 2026/4/3 6:29:04

为何云卓科技C11吊舱能适配多种规格载具?

在无人机巡检、影视航拍、地面移动平台观测等领域&#xff0c;对吊舱设备的核心要求之一便是广泛的平台适配性。云卓科技C11小型高清三轴吊舱正是为此需求设计&#xff0c;其紧凑的机身结构使其能够轻松集成于各种规格尺寸的载具之上。云卓科技在设计C11吊舱时&#xff0c;充分…

作者头像 李华
网站建设 2026/4/8 11:21:20

人才缺口行业以及过剩行业

&#x1f4c9; 人才“相对过剩”行业清单&#xff08;基于2025-2026年就业市场实证&#xff09; 重要澄清&#xff1a; ✅ “人才很多” 岗位需求 < 求职供给&#xff08;结构性过剩&#xff09; ❌ ≠ 行业无价值 / 从业者能力差 &#x1f511; 核心是“供需错配”&#x…

作者头像 李华
网站建设 2026/3/24 20:09:52

PHP毕设项目推荐-基于php的宠物电商猫粮狗粮购物商城的设计与实现基于PHP宠物用品商城网站基于php的宠物商城网站的设计与制作【附源码+文档,调试定制服务】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/2 23:47:17

资讯丨ISO 14001:2026标准最终版即将生效,全条款中英文对照

ISO 14001这一全球应用最广泛的环境管理体系标准&#xff0c;其修订工作已迎来关键节点。最新版本ISO 14001:2026的《国际标准最终草案》&#xff08;以下简称“FDIS”&#xff09;现已完成&#xff0c;进入为期一个月的正式投票表决期&#xff0c;截止日期为 2026年3月2日 。…

作者头像 李华