1. 从“Bug”到“宝藏”:理解useEffect的双次执行哲学
最近在Next.js 18和React项目里,很多朋友都跑来问我同一个问题:“老哥,我明明写了useEffect,依赖数组也是空的[],为啥控制台里的console.log会打印两次?是我的代码写错了吗?” 说实话,我第一次遇到这情况时也懵了一下,本能反应是去检查是不是哪里写了个死循环。但折腾一圈后发现,代码逻辑明明很干净。后来才明白,这根本不是Bug,而是React团队精心设计给你的一份“开发大礼包”。
我们可以把React的严格模式(Strict Mode)想象成一个特别负责任的“代码质检员”。在你把组件代码交付给用户(生产环境)之前,这位质检员会在开发环境里,对你的组件进行一次近乎“变态”的压力测试。它的核心操作就是:快速地把你的组件挂载上去,然后立刻卸载掉,再重新挂载一次。对应到useEffect,就是让那些本应只执行一次的副作用(比如数据请求、事件监听、定时器),在开发环境下故意执行两次。你可能会觉得这多此一举,甚至有点烦人。但它的价值恰恰就在这里——它强迫你去思考并处理组件的“清理”逻辑。
举个我亲身踩过的坑。几年前我写过一个实时数据仪表盘组件,在useEffect里用setInterval每5秒拉取一次数据。在开发时一切正常,因为页面很少手动刷新。但上线后,用户反馈说页面用久了会越来越卡,浏览器内存占用飙升。排查了半天才发现,用户在仪表盘页面和其他标签页之间来回切换时,组件会反复挂载。而我的useEffect里只写了“开启定时器”,忘记在清理函数(return的函数)里“关闭定时器”。结果就是,每切换一次,就多一个定时器在后台默默运行,内存泄漏就这么发生了。如果当时有严格模式的“双次执行”机制提醒我,我就能在开发阶段早早发现这个清理逻辑的缺失,根本不会让问题流到线上。
所以,当你看到useEffect执行了两次,先别急着把它当成问题去“解决”掉。你应该感到庆幸:React正在用这种略显“粗暴”的方式,免费帮你做一次资源泄漏的预演测试。它是在问你:“喂,开发者,如果你的组件瞬间被销毁又重建,你申请的那些资源(网络连接、定时器、订阅事件)能好好地被释放掉吗?” 理解并拥抱这个设计,是写出健壮React应用的第一步。
2. 深入调试:双次执行帮你揪出哪些隐藏问题?
理解了设计意图,我们再来具体看看,这个机制到底能帮你发现哪些平时容易忽略的、但到了生产环境就会爆雷的“定时炸弹”。我把它总结为三类典型场景,这些都是我或者我团队里的伙伴实实在在栽过跟头的地方。
第一类:网络请求的重复与竞态。 这是最常见也最头疼的问题。假设你有一个用户详情页组件,在useEffect里根据用户ID发起一个fetch请求。在开发模式下,由于双次执行,这个请求会瞬间发出两次。如果你的后端API没有做幂等性处理,或者你的前端没有做请求取消,可能会导致数据错乱。更隐蔽的是“竞态条件”:第一次请求比较慢,第二次请求比较快,结果慢的请求后返回,反而覆盖了新的数据。通过观察开发环境下的双请求,你就能提前意识到需要给fetch加上AbortController,或者在依赖变化时正确地取消上一次请求。
import { useEffect, useRef } from 'react';
function UserProfile({ userId }) {
const abortControllerRef = useR

150

被折叠的 条评论
为什么被折叠?



