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

IT技术 reactjs react-hooks
2021-05-12 12:53:14


我是 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;

有人可以帮助我理解为什么它被调用两次,以及如何正确修复代码?非常感谢你!

4个回答

把 console.log 放在 useEffect 里面

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

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

如果日志“我触发一次”被多次触发,则意味着您的问题是两件事之一。

此组件在您的页面中出现不止一次

这个应该很明显,你的组件在页面中出现了几次,每个都会挂载并运行 useEffect

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

组件被强制卸载并在其初始渲染时重新安装。这可能类似于发生在树更高处的“关键”变化。您需要使用此 useEffect 提升每个级别,直到它仅呈现一次。那么您应该能够找到原因或重新安装。

React.Strict 模式开启

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

这个答案由@johnhendirx 指出并由@rangfu 撰写,如果这是您的问题,请参阅链接并给他一些爱。

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

严格模式无法自动为您检测副作用,但它可以通过使它们更具确定性来帮助您发现它们。这是通过有意重复调用以下函数来完成的:

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

严格模式 - Reactjs 文档

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

我用它作为我的替代品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 () => {}
  }, [])

}

此外,还有一个实例,您在屏幕上导航两次。

不确定为什么不将结果置于状态,这是一个调用一次效果的示例,因此您必须在未发布的代码中做了一些使其再次呈现的操作:

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>