React Hooks:即使使用空数组作为参数,也会调用两次 useEffect()
Posted
技术标签:
【中文标题】React Hooks:即使使用空数组作为参数,也会调用两次 useEffect()【英文标题】:React Hooks: useEffect() is called twice even if an empty array is used as an argument 【发布时间】:2020-06-22 10:08:32 【问题描述】:我是 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;
有人可以帮助我理解为什么它被调用两次,以及如何正确修复代码吗? 非常感谢!
【问题讨论】:
你说当你检查console.log但没有console.log时 最初删除了它们,因为我几乎解释了发生的事情,但为了清楚起见,根据您的评论将它们添加回来。 【参考方案1】:把console.log放到useEffect里面
您可能还有其他副作用导致组件重新呈现,但 useEffect 本身只会被调用一次。您可以通过以下代码确定地看到这一点。
useEffect(()=>
/*
Query logic
*/
console.log('i fire once');
,[]);
如果日志“i fire once”被多次触发,则意味着您的问题是 两件事之一。
此组件在您的页面中出现多次
这个应该很明显,你的组件在页面中出现了几次,每次都会挂载并运行 useEffect
树上更高的东西正在卸载和重新安装
组件在其初始渲染时被强制卸载并重新安装。这可能类似于在树的更高处发生的“关键”变化。您需要使用此 useEffect 升级每个级别,直到它只渲染一次。那么您应该能够找到原因或重新安装。
React.Strict 模式开启
StrictMode 渲染组件两次(在开发中而不是在生产中),以便检测代码中的任何问题并向您发出警告(这可能非常有用)。
此答案由@johnhendirx 指出并由@rangfu 撰写,请参阅link,如果这是您的问题,请给他一些爱。
【讨论】:
还有一个注意事项:仔细检查您是否在严格模式下使用 React。如果是这样,请参阅此处:***.com/questions/61254372/… 就我而言,它是卸载和安装。我对 props.searchResult 有一个有效的依赖项,我在搜索完成后显示结果,但显示结果组件的条件是“!isSearching && searchDone”,当我再次单击搜索时,isSearching 设置为 true(卸载)然后返回为假(安装),但旧结果仍处于状态,然后我设置了新结果 - 所以效果被调用了两次。为了解决这个问题,我必须先设置结果,然后将 isSearching 设置为 false。谢谢!我不容易注意到这一点。【参考方案2】:您很可能在启用严格模式的开发环境中检查问题。
要验证是否是这种情况,请搜索
严格模式无法自动为您检测副作用,但可以通过使它们更具确定性来帮助您发现它们。这是通过有意双重调用以下函数来完成的:
传递给 useState、useMemo 或 useReducer 的函数 [...]
Strict Mode - Reactjs docs
这里有类似的问题My React Component is rendering twice because of Strict Mode
【讨论】:
这是我的问题。谢谢!【参考方案3】:我使用它作为我的替代useFocusEffect
。我使用了嵌套的 react 导航堆栈,例如选项卡和抽屉,使用 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 () =>
, [])
另外,还有一个你在屏幕上导航两次的实例。
【讨论】:
【参考方案4】:不确定为什么不将结果置于状态中,这里有一个调用效果的示例,因此您必须在未发布的代码中做了一些使其再次呈现的事情:
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>
【讨论】:
对于遇到多次不希望运行的 useEffect 钩子问题的人,我不建议使用这样的自定义钩子(useIsMounted)增加额外的复杂性。他应该理解为什么会发生这种情况并修复它相应地。 @LuisGurmendez 建议在异步结果设置状态之前检查组件是否仍然挂载是合理的建议。如果 op 对效果进行了意外调用,则问题中发布的代码并未证明这一点。 如果是这种情况,他可以使用 useEffect 回调的返回函数进行适当的清理 es.reactjs.org/blog/2015/12/16/ismounted-antipattern.html @LuisGurmendez 类方法isMounted
与名为useIsMounted
的自定义钩子无关,只要它以use
开头,我就可以调用任何钩子。它们是完全不同的东西。【参考方案5】:
这是用于您的目的的自定义挂钩。它可能对您的情况有所帮助。
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);
【讨论】:
【参考方案6】:我遇到过类似的问题:
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]);
【讨论】:
以上是关于React Hooks:即使使用空数组作为参数,也会调用两次 useEffect()的主要内容,如果未能解决你的问题,请参考以下文章
React Hooks - 我如何实现 shouldComponentUpdate?
React Hooks:确保 useEffect 仅在自定义钩子的数组参数内容更改时运行的惯用方法