即使使用空数组作为参数,useEffect()也会被调用两次


我是reactJS的新手,正在编写代码,以便在从DB加载数据之前,它会显示加载消息,然后在加载后,用加载的数据呈现组件。为此,我使用了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;
有人能帮助我理解为什么它被调用两次,以及如何正确地修复代码吗? 非常感谢!< / p >
199652 次浏览

将console.log放在useEffect中

可能有其他副作用导致组件重新渲染,但useEffect本身只会被调用一次。您可以通过下面的代码确定地看到这一点。

useEffect(()=>{
/*
Query logic
*/
console.log('i fire once');
},[]);

如果日志"i fire one "被触发不止一次就意味着你的问题是

该组件在页面中出现多次

这一点应该很明显,您的组件在页面中出现了几次,每一次都将挂载并运行useEffect

树上更高的东西正在卸载和重新安装

组件被强制卸载并在初始渲染时重新安装。这可以是一个类似“键”的东西;变化发生在树的上层。你需要使用这个useEffect上升到每一层,直到它只呈现一次。然后你应该能找到原因或重新安装。

反应。开启严格模式

StrictMode将组件呈现两次(在开发中,而不是生产中),以便检测代码中的任何问题并警告您(这可能非常有用)。

这个答案是由@johnhendirx指出的,由@rangfu写的,看看链接,如果这是你的问题,给他一些爱。如果您因此而遇到问题,这通常意味着您没有按照预期的目的使用useEffect。在测试文档中有一些关于这个的很好的信息,你可以阅读在这里 . c

不知道为什么你不把结果放在状态,这里有一个例子,调用效果一次,所以你必须在代码中做了一些事情,没有发布,使它再次呈现:

const App = () => {
const [isLoading, setLoad] = React.useState(true)
const [data, setData] = React.useState([])
React.useEffect(() => {
console.log('in effect')
fetch('https://jsonplaceholder.typicode.com/todos')
.then(result => result.json())
.then(data => {
setLoad(false)//causes re render
setData(data)//causes re render
})
},[])
//first log in console, effect happens after render
console.log('rendering:', data.length, isLoading)
return <pre>{JSON.stringify(data, undefined, 2)}</pre>
}


//render app
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

为了防止额外的渲染,你可以在一个状态下合并数据和加载:

const useIsMounted = () => {
const isMounted = React.useRef(false);
React.useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
return isMounted;
};




const App = () => {
const [result, setResult] = React.useState({
loading: true,
data: []
})
const isMounted = useIsMounted();
React.useEffect(() => {
console.log('in effect')
fetch('https://jsonplaceholder.typicode.com/todos')
.then(result => result.json())
.then(data => {
//before setting state in async function you should
//  alsways check if the component is still mounted or
//  react will spit out warnings
isMounted.current && setResult({ loading: false, data })
})
},[isMounted])
console.log(
'rendering:',
result.data.length,
result.loading
)
return (
<pre>{JSON.stringify(result.data, undefined, 2)}</pre>
)
}


//render app
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

我使用这个作为我的替代useFocusEffect。我使用嵌套的反应导航堆栈,如制表符和抽屉和重构使用useEffect没有工作在我的预期。

import React, { useEffect, useState } from 'react'
import { useFocusEffect } from '@react-navigation/native'


const app = () = {


const [isloaded, setLoaded] = useState(false)




useFocusEffect(() => {
if (!isloaded) {
console.log('This should called once')


setLoaded(true)
}
return () => {}
}, [])


}

还有一个例子,你在屏幕上导航了两次。

您很可能在启用严格模式的开发环境中检查问题。 为了验证这种情况,搜索<React.StrictMode>标记并删除它,或者构建用于生产。双重渲染的问题应该消失了。 来自React官方文档

严格模式不能自动为你检测副作用,但它可以通过使它们更具确定性来帮助你发现它们。这是通过有意地双重调用以下函数来实现的:

  • 传递给useState、useMemo或useReducer的函数
  • […]

严格模式- Reactjs docs

这里类似的问题由于严格模式,我的React组件被渲染了两次 < / > < / p >

我遇到过这样的问题:

const [onChainNFTs, setOnChainNFTs] = useState([]);

将触发useEffect两次:

useEffect(() => {
console.log('do something as initial state of onChainNFTs changed'); // triggered 2 times
}, [onChainNFTs]);

我确认组件MOUNTED ONLY ONCE和setOnChainNFTs没有被调用不止一次-所以这不是问题所在。

我通过将onChainNFTs的初始状态转换为null并进行空检查来修复它。

如。

const [onChainNFTs, setOnChainNFTs] = useState(null);
useEffect(() => {
if (onChainNFTs !== null) {
console.log('do something as initial state of onChainNFTs changed'); // triggered 1 time!
}
}, [onChainNFTs]);

下面是为您的目的定制的钩子。这对你的情况可能有帮助。

import {
useRef,
EffectCallback,
DependencyList,
useEffect
} from 'react';


/**
*
* @param effect
* @param dependencies
* @description Hook to prevent running the useEffect on the first render
*
*/
export default function useNoInitialEffect(
effect: EffectCallback,
dependancies?: DependencyList
) {
//Preserving the true by default as initial render cycle
const initialRender = useRef(true);


useEffect(() => {
   

let effectReturns: void | (() => void) = () => {};
    

/**
* Updating the ref to false on the first render, causing
* subsequent render to execute the effect
*
*/
if (initialRender.current) {
initialRender.current = false;
} else {
effectReturns = effect();
}


/**
* Preserving and allowing the Destructor returned by the effect
* to execute on component unmount and perform cleanup if
* required.
*
*/
if (effectReturns && typeof effectReturns === 'function') {
return effectReturns;
}
return undefined;
}, dependancies);
}


请检查你的index.js

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

删除<包装器 现在应该触发一次

root.render(
<App />
);
< p >删除& lt; React.StrictMode>从index.js 这段代码将是

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

root.render(
<App />
);

React StrictMode在开发服务器上渲染组件两次

这是我们使用React.StrictMode时ReactJS的特性。StrictMode为它的后代节点激活额外的检查和警告。因为应用程序不应该崩溃的情况下,任何不良的做法在代码。我们可以说StrictMode是一个安全检查,用于两次验证组件以检测错误。

你会得到这个<React.StricyMode>在组件的根。

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

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

没什么好担心的。当你在开发模式下运行React时。它有时会运行两次。在刺激环境中测试它,您的useEffect将只运行一次。别担心! !

如果你正在使用Next js,将reactStrictMode从“;true"错误:

将此添加到你的next.config.js中

reactStrictMode: false,

我在React 18中找到了一个非常好的解释。在React中调用UseEffect两次

注意:在生产中,它可以正常工作。在开发环境中的严格模式下,有意增加两次安装,以处理错误和所需的清理。

在我的例子中是严格模式。删除索引处的严格模式组件。TSX或index.jsx

反应根>index.js祝辞删除<React.StrictMode>包装

我使用CodeSandbox和删除防止了这个问题。

CodeSandbox_sample

新的React文档(目前处于测试阶段)有一个章节精确地描述了这种行为:

如何处理效果发射两次在开发

从文档中可以看出:

通常,答案是实现清理功能。清除函数应该停止或撤消Effect正在做的任何事情。经验法则是,用户不应该能够区分Effect运行一次(如在生产中)和设置→清理→设置序列(如您在开发中看到的)。

所以这个警告应该让你仔细检查你的useEffect,通常意味着你需要实现一个清理函数。

如果有人使用NextJS 13来这里,为了删除严格模式,你需要在next.config.js文件上添加以下内容:

const nextConfig = {
reactStrictMode: false
}
module.exports = nextConfig

当我创建项目时,它使用“严格模式”;默认情况下,这就是为什么我必须显式设置它。