为什么你的 useEffect 执行了两次?原因和解决办法

更新日期: 2025-10-21 阅读: 19 标签: hooks

如果你在 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 在帮你做质量检查。


什么时候需要特别注意?

虽然这个设计是为了帮助我们发现问题,但在某些情况下需要特别处理:

  1. 数据请求:如上所述,需要避免重复请求

  2. 定时器:需要确保正确清理

  3. 事件监听:防止重复添加监听器

  4. 第三方库的初始化:有些库只需要初始化一次

对于这些情况,使用 useRef 作为标记是一个简单有效的解决方案。


更好的开发习惯

理解这个特性后,你可以养成更好的开发习惯:

  • 总是在 useEffect 中返回清理函数

  • 对于事件监听,记得在清理函数中移除

  • 对于定时器,记得在清理函数中清除

  • 考虑使用专门的状态管理库来处理数据请求


总结

一开始,这个特性可能会让人困惑,但理解其设计目的后,你会发现它实际上是在帮助你写出更健壮的代码。

关键点:

  • useEffect 在开发环境执行两次是正常现象

  • 这是 React 严格模式的特性,不是 bug

  • 生产环境会正常执行一次

  • 可以使用 useRef 标记来控制只执行一次

  • 推荐使用专业的数据请求库来处理复杂场景

通过理解这个设计,你不仅能解决眼前的问题,还能写出更高质量、更少 bug 的 React 代码。记住,这个特性是你代码质量的守护者,而不是麻烦制造者。

本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!

链接: https://fly63.com/article/detial/13023

React将引入Hooks,你怎么看?

近日,据 MIT Technology Review 报道,一位名为“Repairnator”的机器人在 GitHub 上“卧底”数月,查找错误并编写和提交修复补丁,结果有多个补丁成功通过并被采纳,这位 Repairnator 到底是如何拯救程序员于水火的呢?

精通React今年最劲爆的新特性——React Hooks

你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗?你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗?你在还在为组件中的this指向而晕头转向吗?这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张。

使用react hooks实现自己的context-redux

我们将userReducer函数返回的原始dispath命名为origin_dispatch,自定义dispatch函数,当action为函数的时候,我们执行action函数,并将origin_dispatch当作参数传进去;action不是函数,直接调用origin_dispatch,不做处理

useEffect Hook 是如何工作的?

使用useEffect 就像瑞士军刀。它可以用于很多事情,从设置订阅到创建和清理计时器,再到更改ref的值。与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。

React Hooks 你真的用对了吗?

从 React Hooks 正式发布到现在,我一直在项目使用它。但是,在使用 Hooks 的过程中,我也进入了一些误区,导致写出来的代码隐藏 bug 并且难以维护。这篇文章中,我会具体分析这些问题,并总结一些好的实践,以供大家参考

如何用 Hooks 来实现 React Class Component 写法?

Hooks 的 API 可以参照 React 官网。本文主要是结合 Demo 详细讲解如何用 Hooks 来实现 React Class Component 写法,让大家更深的理解 Hooks 的机制并且更快的入门。 注意:Rax 的写法和 React 是一致的

React-Hooks

以下是上一代标准写法类组件的缺点,也正是hook要解决的问题,型组件很难拆分和重构,也很难测试。业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。

React Hooks与setInterval

Hooks出来已经有段时间了,相信大家都用过段时间了,有没有小伙伴们遇到坑呢,我这边就有个 setInterval 的坑,和小伙伴们分享下解决方案。写个 count 每秒自增的定时器,如下写法结果,界面上 count 为 1 ?

React Hooks 底层解析[译]

对于 React 16.7 中新的 hooks 系统在社区中引起的骚动,我们都有所耳闻了。人们纷纷动手尝试,并为之兴奋不已。一想到 hooks 时它们似乎是某种魔法,React 以某种甚至不用暴露其实例

React Hooks实践

9月份开始,使用了React16.8的新特性React Hooks对项目进行了重构,果然,感觉没有被辜负,就像阮一峰老师所说的一样,这个 API 是 React 的未来。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!