为什么你的 useEffect 执行了两次?原因和解决办法
如果你在 react 项目里写了一个 useEffect,本来希望它只运行一次,却在控制台看到它执行了两次。不用担心,很多人都遇到过这个问题。
你可能会想:
“这是 React 的 bug 吗?”
“我的代码写错了?”
“是不是哪里出了问题?”
实际上,这不是 bug,而是 React 故意这样设计的。下面我们来详细解释原因,并告诉你正确的处理方法。
你很可能写了这样的代码:
useEffect(() => {
console.log("Effect 执行了");
}, []);你期待它只打印一次,但在开发环境下却看到两次输出。
原因:React 的严格模式
从 React 18 开始,使用 create-react-app、Next.js 等工具创建的项目,在开发模式下默认开启了严格模式。
严格模式在开发环境下,会把某些生命周期相关的逻辑多执行一次。这样做的目的是帮助你提前发现代码中的问题,比如没有正确清理的订阅、对外部数据的意外修改、忘记清理的异步操作等。
<React.StrictMode>
<App />
</React.StrictMode>重要提示:这种“双执行”只发生在开发环境。当你打包部署到生产环境后,useEffect 会按照预期只执行一次。
React 为什么这样设计?
React 团队希望通过这种方式,让你在开发阶段就发现潜在的问题。它模拟了组件的挂载和卸载过程,帮你检查:是否忘记了清理定时器或事件监听
在渲染过程中是否有意外的副作用
是否有重复的数据请求
你可以把这理解为一次“安全演习”:在正式上线前,先在测试环境把可能的问题暴露出来。
实际例子:数据请求为什么发送了两次
假设你在 useEffect 中请求数据:useEffect(() => {
fetch("/api/data")
.then(res => res.json())
.then(setData);
}, []);在开发环境下,这个效果会被执行两次,导致发送了两次 API 请求。这显然不是我们想要的结果。
解决办法:使用标记控制只执行一次
const hasFetched = useRef(false);
useEffect(() => {
if (!hasFetched.current) {
hasFetched.current = true;
fetch("/api/data")
.then(res => res.json())
.then(setData);
}
}, []);另一个更好的方案是使用专门的数据请求库,比如 React Query。这些库已经帮你处理了这类问题。
哪些情况会被执行两次?
在 React 18 的严格模式下,组件初次挂载时,以下内容会被执行两次:useEffect
useLayoutEffect
类组件的 componentDidMount 和 componentWillUnmount
需要注意的是,React 并没有进行两次渲染。实际的流程是:组件挂载 → 卸载 → 再次挂载,通过这个过程来验证你的副作用代码是否安全。
如何确认这只是开发环境的行为?
最直接的方法是构建生产版本并运行:
npm run build
npm start在生产模式下,你会看到 useEffect 只执行了一次。所以不必担心,这不是系统出了问题,而是 React 在帮你做质量检查。
什么时候需要特别注意?
虽然这个设计是为了帮助我们发现问题,但在某些情况下需要特别处理:数据请求:如上所述,需要避免重复请求
定时器:需要确保正确清理
事件监听:防止重复添加监听器
第三方库的初始化:有些库只需要初始化一次
对于这些情况,使用 useRef 作为标记是一个简单有效的解决方案。
更好的开发习惯
理解这个特性后,你可以养成更好的开发习惯:总是在 useEffect 中返回清理函数
对于事件监听,记得在清理函数中移除
对于定时器,记得在清理函数中清除
考虑使用专门的状态管理库来处理数据请求
总结
一开始,这个特性可能会让人困惑,但理解其设计目的后,你会发现它实际上是在帮助你写出更健壮的代码。关键点:
useEffect 在开发环境执行两次是正常现象
这是 React 严格模式的特性,不是 bug
生产环境会正常执行一次
可以使用 useRef 标记来控制只执行一次
推荐使用专业的数据请求库来处理复杂场景
通过理解这个设计,你不仅能解决眼前的问题,还能写出更高质量、更少 bug 的 React 代码。记住,这个特性是你代码质量的守护者,而不是麻烦制造者。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!