useEffect中的无限循环

问题描述 投票:21回答:5

我一直在使用React 16.7-alpha中的新钩子系统,当我正在处理的状态是一个对象或数组时,它会在useEffect中陷入无限循环。

首先,我使用useState并使用如下的空对象启动它:

const [obj, setObj] = useState({});

然后,在useEffect中,我使用setObj将其再次设置为空对象。作为第二个参数,我传递[obj],希望如果对象的内容没有改变,它不会更新。但它不断更新。我想因为无论内容如何,​​这些都是不同的对象,让React认为它在不断变化?

useEffect(() => {
  setIngredients({});
}, [ingredients]);

数组也是如此,但作为一个原语,它不会像预期的那样卡在循环中。

使用这些新的钩子,在检查天气内容是否发生变化时,我应该如何处理对象和数组?

reactjs react-hooks
5个回答
25
投票

将空数组作为useEffect的第二个参数传递使其仅在mount和unmount上运行,从而停止任何无限循环。

useEffect(() => {
  setIngredients({});
}, []);

https://www.robinwieruch.de/react-hooks/的React hooks博客文章中向我澄清了这一点


20
投票

有同样的问题。我不知道他们为什么不在文档中提到这一点。只想添加一点Tobias Haugen的答案。

要在每个组件/父级重新渲染器中运行,您需要使用:

  useEffect(() => {

    // don't know where it can be used :/
  })

要在组件装载后只运行一次(将渲染一次),您需要使用:

  useEffect(() => {

    // do anything only one time if you pass empty array []
    // keep in mind, that component will be rendered one time (with default values) before we get here
  }, [] )

要在组件装载和数据/数据2上运行任何一次更改:

  const [data, setData] = useState(false)
  const [data2, setData2] = useState('default value for first render')
  useEffect(() => {

// if you pass some variable, than component will rerender after component mount one time and second time if this(in my case data or data2) is changed
// if your data is object and you want to trigger this when property of object changed, clone object like this let clone = JSON.parse(JSON.stringify(data)), change it clone.prop = 2 and setData(clone).
// if you do like this 'data.prop=2' without cloning useEffect will not be triggered, because link to data object in momory doesn't changed, even if object changed (as i understand this)
  }, [data, data2] )

我大部分时间都在使用它:

export default function Book({id}) { 
  const [book, bookSet] = useState(false) 

  useEffect(() => {
    loadBookFromServer()
  }, [id]) // every time id changed, new book will be loaded

  async function loadBookFromServer() {
    let response = await fetch('api/book/' + id)
    response  = await response.json() 
    bookSet(response)
  }

  if (!book) return false //first render, when useEffect did't triggered yet we will return false

  return <div>{JSON.stringify(book)}</div>  
}

5
投票

如文档(https://reactjs.org/docs/hooks-effect.html)中所述,当您希望在每次渲染之后执行某些代码时,可以使用useEffect钩子。来自文档:

每次渲染后useEffect都会运行吗?是!

如果要自定义此选项,可以按照稍后出现在同一页面(https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects)中的说明进行操作。基本上,useEffect方法接受第二个参数,React将检查以确定是否必须再次触发效果。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

您可以将任何对象作为第二个参数传递。如果此对象保持不变,则只有在第一次安装后才会触发您的效果。如果对象发生变化,则会再次触发效果。


3
投票

我也遇到了同样的问题,我通过确保在第二个参数[]中传递原始值来修复它。

如果你传递一个对象,React将只存储对象的引用并在引用更改时运行效果,这通常是每个单一时间(我现在不知道如何)。

解决方案是传递对象中的值。你可以试试,

const obj = { keyA: 'a', keyB: 'b' }

useEffect(() => {
  // do something
}, [Object.values(obj)]);

要么

const obj = { keyA: 'a', keyB: 'b' }

useEffect(() => {
  // do something
}, [obj.keyA, obj.keyB]);

2
投票

我不确定这是否适合你,但你可以尝试添加.length这样:

useEffect(() => {
        // fetch from server and set as obj
}, [obj.length]);

在我的情况下(我正在获取数组!)它在mount上获取数据,然后仅在更改时再次获取并且它不会进入循环。


0
投票

你的无限循环是由于圆度

useEffect(() => {
  setIngredients({});
}, [ingredients]);

setIngredients({});将改变ingredients的值(每次都会返回一个新的引用),这将运行setIngredients({})。要解决此问题,您可以使用以下任一方法

  1. 将另一个参数传递给useEffect
const timeToChangeIngrediants = .....
useEffect(() => {
  setIngredients({});
}, [timeToChangeIngrediants ]);

setIngrediants改变时,timeToChangeIngrediants将会运行。

  1. 我不确定哪些用例在更改后会改变成分。但如果是这种情况,则将Object.values(ingrediants)作为useEffect的第二个参数传递。
useEffect(() => {
  setIngredients({});
}, Object.values(ingrediants));
© www.soinside.com 2019 - 2024. All rights reserved.