React Hooks: 如果将一个空数组作为参数传递给useEffect(),它仍然会被调用两次。

608

我正在编写代码,以便在从数据库加载数据之前显示加载消息,然后在加载完成后使用加载的数据渲染组件。为了实现这一点,我同时使用了useState钩子和useEffect钩子。以下是代码:

问题是,当我使用console.log进行检查时,useEffect会触发两次。因此,代码会查询相同的数据两次,这是应该避免的。

以下是我编写的代码:

import React from 'react';
import './App.css';
import {useState,useEffect} from 'react';
import Postspreview from '../components/Postspreview'

const indexarray=[]; //The array to which the fetched data will be pushed

function Home() {
   const [isLoading,setLoad]=useState(true);
   useEffect(()=>{
      /*
      Query logic to query from DB and push to indexarray
      */
          setLoad(false);  // To indicate that the loading is complete
    })
   },[]);
   if (isLoading===true){
       console.log("Loading");
       return <div>This is loading...</div>
   }
   else {
       console.log("Loaded!"); //This is actually logged twice.
       return (
          <div>
             <div className="posts_preview_columns">
             {indexarray.map(indexarray=>
             <Postspreview
                username={indexarray.username}
                idThumbnail={indexarray.profile_thumbnail}
                nickname={indexarray.nickname}
                postThumbnail={indexarray.photolink}
             />
             )}
            </div>
         </div>  
         );
    }
}

export default Home;

为什么它被调用了两次,以及如何正确修复代码?

3
你说当你查看控制台日志(console.log),但是没有找到控制台日志。 - Joe Lloyd
3
起初我将它们删除了,因为我已经解释了发生了什么,但根据您的评论,我把它们重新加回来以增加清晰度。 - J.Ko
我的解决方案在 https://stackoverflow.com/a/72676006/2184182,我在这里分享。我猜可以给你帮助。 - Serkan KONAKCI
3
对于使用 React 18 的人 https://dev59.com/OlEG5IYBdhLWcg3wJlLh。翻译:针对React 18用户的问题,链接为https://dev59.com/OlEG5IYBdhLWcg3wJlLh。 - Youssouf Oumar
3
我相信这是因为<React.Strict>发生的。尝试将<React.Strict><Home></React.Strict>替换为只有<Home>。严格模式在本地渲染组件两次,在生产环境中渲染一次。 - Dhanesh Mane
23个回答

1025
将console.log放在useEffect内部
可能你有其他副作用导致组件重新渲染,但是useEffect本身只会被调用一次。你可以通过以下代码确保这一点。
useEffect(()=>{
      /*
      Query logic
      */
      console.log('i fire once');
},[]);

如果触发了多次日志“我只触发一次”,那么问题可能是以下三种情况之一。

此组件在页面中出现多次

这个很明显,你的组件在页面上出现了多次,每个组件都会挂载和运行useEffect。

树上方的某个组件正在卸载和重新挂载

该组件在初始渲染时被强制卸载和重新挂载。这可能是因为树上方发生了类似于“key”更改的情况。你需要逐级检查每个层级的useEffect,直到它只渲染一次。然后你就能找到引起重新挂载的原因。

React.Strict模式开启

StrictMode会在开发环境中将组件渲染两次(生产环境不会),以便检测代码中的任何问题并警告您(这非常有用)。

这个答案是由@johnhendirx指出并由@rangfu撰写的,点击链接并给他一些支持,如果这是你的问题。如果你因此遇到问题,通常意味着你没有正确使用useEffect。关于这个问题,文档中有一些很好的信息,你可以在这里阅读。

357
还有一点需要考虑:请确认是否在React的严格模式下使用。如果是,请参考这里:https://dev59.com/CVIH5IYBdhLWcg3wGY8n - jonhendrix
2
非常感谢您提供如此全面的答案。在我的情况下,问题出在 React 18 中可怕的严格模式。 - Ankan Bag
31
严格模式应该在开发环境下发出警告,告知其执行这种操作。我真的希望它不要默默地执行。 - Grant Curell
3
对我来说是React.StrictMode。感谢指出! - Ethan
1
@slumbering,你不应该禁用它,而应该修改你的代码以在严格模式下更好地工作。 - Joe Lloyd
显示剩余7条评论

282

从index.js中删除<React.StrictMode>代码将会

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
root.render(
    <App />
);

React严格模式在开发服务器上会渲染组件两次


2
哦,StrictMode是罪魁祸首。Yeasin,如果你能多解释一下就太好了。 - Yash Pokar
7
根据我的理解,React StrictMode 会像提到的那样将组件渲染两次。为什么?为了识别具有不安全生命周期的组件,您可以在此处查看:https://reactjs.org/docs/strict-mode.html#identifying-unsafe-lifecycles 。换句话说,确保组件能够经受住挂载和卸载的考验。如果您正在使用 useEffect,则最好编写清除函数。如果您的 useEffect 中真的有一些很耗费资源的操作,可能需要将它们完全移出 useEffect。 - Sandy B
2
移除StrictMode是最后要做的事情。它有其存在的目的。请参见https://dev59.com/OlEG5IYBdhLWcg3wJlLh。 - Youssouf Oumar
1
我本来想说的是,@YoussoufOumar,StrictMode不应该被篡改。 谢谢你指出这一点。 - Bravo Stack

65
你很可能在使用启用了严格模式的开发环境中查找问题。为验证这一点,请搜索<React.StrictMode>标签并移除它,或构建生产环境。双重渲染问题应该会消失。来自React官方文档。
严格模式不能自动检测副作用,但可以通过使副作用更加确定性来帮助您发现它们。这是通过有意双重调用以下函数来完成的:useState、useMemo或useReducer函数等。Strict Mode - Reactjs docs 类似问题请参见My React Component is rendering twice because of Strict Mode

但是 useEffect 不在被双重调用的钩子列表中。事实上,在 Strict Mode 的文档中并没有提到 useEffect。在我的应用程序中,似乎在某些情况下会发生双重调用,而在其他情况下则不会。 - Qwertie
1
我没有检查文档中是否提到了useEffect,但是它位于渲染函数内部,所以这些注释也应该适用于它。 - marcelocra

30
根据其他人的指出,这很可能是由 React 18.0 中引入的严格模式特性导致的。
我写了一篇博文,解释了为什么会发生这种情况以及你可以采取哪些方法来解决它。
但如果你只想看代码,这里有:
const initialized = useRef(false)

useEffect(() => {
  if (!initialized.current) {
    initialized.current = true

    // My actual effect logic...
    ...
  }
}, [])

或者作为一个可重复使用的挂钩:
import type { EffectCallback } from "react"
import { useEffect, useRef } from "react"

export function useOnMountUnsafe(effect: EffectCallback) {
  const initialized = useRef(false)

  useEffect(() => {
    if (!initialized.current) {
      initialized.current = true
      effect()
    }
  }, [])
}

请记住,只有在绝对必要的情况下才应采用这种解决方案!

强调这应该作为一个解决问题的后备方案而不是一个“解决方案”。 - New Alexandria
为什么把它作为最后的选择?.. 使用useRef作为哨兵标志有什么问题吗? - undefined

27

请检查您的index.js文件

  <React.StrictMode>
    <App />
  </React.StrictMode>

移除 <React.StrictMode> 包装器,现在你应该只触发一次

root.render(
    <App />
);

2
移除StrictMode是最后要做的事情。它有其存在的目的。请参见https://dev59.com/OlEG5IYBdhLWcg3wJlLh - Youssouf Oumar

26

React根 > index.js > 移除<React.StrictMode>包装器


8
目前您的回答不够清晰,请编辑并添加更多细节,以帮助其他人了解这如何回答该问题。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community
2
这对我有用。我要点个赞。 - TonyCruze

20
如果您正在使用Next.js,请将reactStrictMode从“true”更改为false:
将以下内容添加到您的next.config.js文件中。
reactStrictMode: false,

1
reactStrictMode默认为true,将其设置为false可以解决问题,但我们还应该记住,正如nextjs文档建议的那样,保持reactStrictMode为true也可能是有益的:https://nextjs.org/docs/api-reference/next.config.js/react-strict-mode - Elyas Karimi
2
移除StrictMode是最后要做的事情。它有其存在的目的。请参见https://dev59.com/OlEG5IYBdhLWcg3wJlLh - Youssouf Oumar
在Next.js中为我工作,谢谢。 - malik kurosaki

19

当我们使用React.StrictMode时,它是ReactJS的一个特性。StrictMode会针对其子节点激活额外的检查和警告,因为在代码中存在任何不良实践时,应用程序不应崩溃。我们可以说StrictMode是一项安全检查,可以对组件进行两次验证以检测错误。

您可以在组件的根部获取此。

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

如果您想限制组件只渲染两次,您可以删除<React.StrictMode>并检查它。但是,在出现糟糕的代码实践时,使用StrictMode检测运行时错误是必要的。


10

我在 React 18 中找到了一个非常好的关于组件挂载两次的解释。

注意:在生产环境中,它正常工作。在开发环境的严格模式下,为处理错误和必要的清理而有意添加了两次挂载。

React 中 UseEffect 被调用两次


8
新的React文档(目前处于beta测试阶段)有一节描述了这种行为:如何处理开发中Effect触发两次的问题。从文档中可以得知:
通常情况下,答案是实现清理函数。 清理函数应该停止或撤消Effect正在执行的任何操作。 经验法则是用户不应能够区分Effect只运行一次(在生产中)和一个设置→清理→设置序列(在开发中可以看到)。
因此,如果收到此警告,请仔细检查您的useEffect,并确保您实现了清理函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接