useEffect中的无限循环

Tob*_*gen 45 reactjs react-hooks

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

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

const [obj, setObj] = useState({});
Run Code Online (Sandbox Code Playgroud)

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

useEffect(() => {
  setIngredients({});
}, [ingredients]);
Run Code Online (Sandbox Code Playgroud)

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

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

Tob*_*gen 49

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

useEffect(() => {
  setIngredients({});
}, []);
Run Code Online (Sandbox Code Playgroud)

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

  • 这并不能解决问题,您需要传递钩子使用的依赖项 (41认同)
  • 如果希望它在对象更新时运行怎么办? (8认同)
  • 正如 Dan Abramov 所说,当 setIngrediets 是依赖项时使用空数组是一种反模式。不要将 useEffectt 视为 componentDidMount() 方法 (5认同)
  • 请注意,卸载时效果将运行清理功能(如果您已指定)。实际效果不会在卸载时运行。https://reactjs.org/docs/hooks-effect.html#example-using-hooks-1 (3认同)
  • 它实际上是一个空数组,而不是您应该传入的空对象。 (2认同)
  • 由于上面的人没有提供有关如何正确解决此问题的链接或资源,我建议即将到来的 duckduckgoers 看看这个,因为它解决了我的无限循环问题:/sf/ask/3966053521/ -props-回调函数导致无限循环 (2认同)

RTW*_*RTW 40

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

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

  useEffect(() => {

    // don't know where it can be used :/
  })
Run Code Online (Sandbox Code Playgroud)

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

  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
  }, [] )
Run Code Online (Sandbox Code Playgroud)

一次在组件安装和数据/ data2更改运行任何内容:

  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] )
Run Code Online (Sandbox Code Playgroud)

我大多数时候如何使用它:

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

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

  // Remeber that it's not always safe to omit functions from the list of dependencies. Explained in comments.
  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>  
}
Run Code Online (Sandbox Code Playgroud)

  • 根据 React 常见问题解答,[从依赖项列表中省略函数是不安全的](https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from -依赖项列表)。 (3认同)
  • 当然,如果您不使用组件范围中的**任何**值,那么可以安全地省略。但从纯粹的 vue 设计/架构角度来看,这不是一个好的实践,因为如果您需要使用 props,它需要您将整个函数移到效果内部,并且最终可能会得到一个 useEffect 方法,该方法将使用代码行数惊人。 (2认同)
  • 这确实很有帮助,调用 useEffect 挂钩内的异步函数仅在值更改时更新。谢了哥们 (2认同)

Jka*_*nen 19

如果您正在构建自定义钩子,有时会导致无限循环,默认情况如下

function useMyBadHook(values = {}) {
    useEffect(()=> { 
           /* This runs every render, if values is undefined */
        },
        [values] 
    )
}
Run Code Online (Sandbox Code Playgroud)

解决方法是使用相同的对象,而不是在每次函数调用时都创建一个新对象:

const defaultValues = {};
function useMyBadHook(values = defaultValues) {
    useEffect(()=> { 
           /* This runs on first call and when values change */
        },
        [values] 
    )
}
Run Code Online (Sandbox Code Playgroud)

如果您在组件代码中遇到此问题,如果您使用 defaultProps 而不是 ES6 默认值,则循环可能会得到修复

function MyComponent({values}) {
  useEffect(()=> { 
       /* do stuff*/
    },[values] 
  )
  return null; /* stuff */
}

MyComponent.defaultProps = {
  values = {}
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢!这对我来说并不明显,而这正是我遇到的问题。 (2认同)

bes*_*rtm 11

如果在useEffect末尾包含空数组:

useEffect(()=>{
        setText(text);
},[])
Run Code Online (Sandbox Code Playgroud)

它会运行一次。

如果您还包括数组上的参数:

useEffect(()=>{
            setText(text);
},[text])
Run Code Online (Sandbox Code Playgroud)

只要文本参数改变,它就会运行。

  • 空数组会导致 eslinter 发出警告。 (2认同)

Roc*_*que 10

如文档(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
Run Code Online (Sandbox Code Playgroud)

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


Ben*_*arp 10

你的无限循环是由于循环

useEffect(() => {
  setIngredients({});
}, [ingredients]);
Run Code Online (Sandbox Code Playgroud)

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

  1. 将不同的第二个参数传递给 useEffect
const timeToChangeIngrediants = .....
useEffect(() => {
  setIngredients({});
}, [timeToChangeIngrediants ]);
Run Code Online (Sandbox Code Playgroud)

setIngrediants将在timeToChangeIngrediants更改时运行。

  1. 我不确定一旦更改成分,什么用例证明更改成分是合理的。但如果是这种情况,您Object.values(ingrediants)可以将第二个参数作为 useEffect 传递。
useEffect(() => {
  setIngredients({});
}, Object.values(ingrediants));
Run Code Online (Sandbox Code Playgroud)


小智 9

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

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

就我而言(我正在获取一个数组!)它在装载时获取数据,然后再次仅在更改时获取数据,并且没有进入循环。

  • 如果数组中的某个项目被替换怎么办?在这种情况下,数组的长度将相同,并且效果将不会运行! (3认同)

Din*_*yan 7

我也遇到了同样的问题,并通过确保在第二个参数中传递原始值来解决了该问题 []

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

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

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

useEffect(() => {
  // do something
}, [Object.values(obj)]);
Run Code Online (Sandbox Code Playgroud)

要么

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

useEffect(() => {
  // do something
}, [obj.keyA, obj.keyB]);
Run Code Online (Sandbox Code Playgroud)

  • 另一种方法是使用 useMemo 创建此类值,这样可以保留引用,并且将依赖项的值评估为相同的值 (5认同)
  • @helado您可以使用 useMemo() 作为值或 useCallback() 作为函数 (2认同)

thi*_*ign 7

当将复杂对象作为状态并从以下位置更新时,我经常遇到无限重新渲染useRef

const [ingredients, setIngredients] = useState({});

useEffect(() => {
  setIngredients({
    ...ingredients,
    newIngedient: { ... }
  });
}, [ingredients]);
Run Code Online (Sandbox Code Playgroud)

在这种情况下,eslint(react-hooks/exhaustive-deps)迫使我(正确地)添加ingredients到依赖项数组中。然而,这会导致无限的重新渲染。与此线程中的某些人所说的不同,这是正确的,并且您无法将ingredients.someKeyingredients.length放入依赖项数组中。

解决方案是 setter 提供您可以参考的旧值。您应该使用它,而不是直接引用ingredients

const [ingredients, setIngredients] = useState({});

useEffect(() => {
  setIngredients(oldIngedients => {
    return {
      ...oldIngedients,
      newIngedient: { ... }
    }
  });
}, []);
Run Code Online (Sandbox Code Playgroud)