如何解决React useEffect 无限循环

使用React 的 useEffect后出现无限循环的几种情况,如何修复。

如何解决React useEffect 无限循环

React 的 useEffect 让用户可以处理他们应用程序的一些效果,例如:

  • 从网络获取数据:通常,应用程序在第一次装载时获取并填充数据。 这可以通过 useEffect 函数实现
  • 操作 UI:应用程序应响应按钮单击事件(例如,打开菜单)
  • 设置或结束计时器:如果某个变量达到预定义的值,则内置计时器应自行停止或启动

尽管 useEffect Hook 的使用在 React 生态系统中很常见,但它需要时间来掌握。 正因为如此,许多新手开发人员配置他们的 useEffect 函数时会导致无限循环问题。 在本文中,您将了解臭名昭著的无限循环以及如何解决它。

这就是我们今天要学习的内容:

  • 什么导致无限循环以及如何解决它们
    • 在依赖数组中不传递依赖
    • 使用函数作为依赖项
    • 使用数组作为依赖项
    • 传递不正确的依赖项
在依赖数组中不传递依赖

如果您的 useEffect 函数不包含任何依赖项,则会发生无限循环,例如:

function App() {
  const [count, setCount] = useState(0); //initial value of this 
  useEffect(() => {
    setCount((count) => count + 1); //increment this Hook
  }); //no dependency array.
  return (
    <div className="App">
      <p> value of count: {count} </p>
    </div>
  );
}

如果没有依赖关系,则默认情况下会在每个更新周期触发 useEffect。因此,这里的应用程序将在每次渲染时执行 setCount 函数。因此,这会导致无限循环:

是什么导致了这个问题?让我们逐步分解我们的问题:

  • 在第一次渲染时,React检查count的值。由于count为0,程序执行useEffect函数
  • 稍后,useEffect 调用setCount方法并更新count的值
  • 之后,React重新渲染UI以显示更新后的count值
  • 此外,由于 useEffect 在每个渲染周期运行,它重新调用setCount函数
  • 由于上述步骤发生在每次渲染上,这会导致您的应用程序崩溃


如何解决此问题

为了缓解这个问题,我们必须使用依赖数组。这告诉 React 只有在特定值更新时才调用 useEffect,附加一个空白数组作为依赖项,如下所示:

useEffect(() => {
  setCount((count) => count + 1);
}, []); //empty array as second argument.

这告诉 React 在第一次挂载时执行 setCount 函数。

使用函数作为依赖项

如果你将一个方法传递给你的 useEffect 依赖数组,React 会抛出一个错误,表明你有一个无限循环:

function App() {
  const [count, setCount] = useState(0);

  function logResult() {
    return 2 + 2;
  }
  useEffect(() => {
    setCount((count) => count + 1);
  }, [logResult]); //set our function as dependency
  return (
    <div className="App">
      <p> value of count: {count} </p> {/*Display the value of count*/}
    </div>
  );
}

在这个片段中,我们将 logResult 方法传递给 useEffect 数组。理论上,React 只需要在第一次渲染时增加 count 的值。

是什么导致了这个问题?

1.要记住的一件事是 useEffect 使用了一个称为浅比较的概念。它这样做是为了验证依赖项是否已更新
2.这里的问题是,在每次渲染过程中,React 重新定义了 logResult 的引用
3.结果,这会在每个循环中重新触发 useEffect 函数
4.因此,React 会调用 setCount Hook,直到您的应用遇到更新深度错误。这会在您的程序中引入错误和不稳定性

如何解决此问题

一种解决方案是使用 useCallback Hook。这允许开发人员记住他们的函数,从而确保参考值保持不变。由于参考值稳定,React 不应该无限地重新渲染 UI:

const logResult = useCallback(() => {
  return 2 + 2;
}, []); //logResult is memoized now.
useEffect(()=> {
  setCount((count)=> count+1);
},[logResult]); //no infinite loop error, since logResult reference stays the same.

结果将是:

使用数组作为依赖项

将数组变量传递到您的依赖项中也会运行无限循环,考虑这个代码示例:

const [count, setCount] = useState(0); //iniital value will be 0.
const myArray = ["one", "two", "three"];

useEffect(() => {
  setCount((count) => count + 1); //just like before, increment the value of Count
}, [myArray]); //passing array variable into dependencies

我们将 myArray 变量传递给我们的依赖参数。

是什么导致了这个问题?

既然 myArray 的值在整个程序中都不会改变,为什么我们的代码会多次触发 useEffect 呢?

1.在这里,回想一下 React 使用浅比较来检查依赖项的引用是否已更改。
2.由于对 myArray 的引用在每次渲染时不断变化,useEffect 将触发 setCount 回调
3.因此,由于 myArray 的参考值不稳定,React 会在每个渲染周期调用 useEffect。最终,这会导致您的应用程序崩溃

如何解决此问题

为了解决这个问题,我们可以使用一个 useRefHook。这将返回一个可变对象,以确保引用不会更改:

const [count, setCount] = useState(0);
//extract the 'current' property and assign it a value
const { current: myArray } = useRef(["one", "two", "three"]);

useEffect(() => {
  setCount((count) => count + 1);
}, [myArray]); //the reference value is stable, so no infinite loop
将对象作为依赖项传递

在 useEffect 依赖数组中使用对象也会导致无限循环问题,代码如下:

const [count, setCount] = useState(0);
const person = { name: "Rue", age: 17 }; //create an object
useEffect(() => {
  //increment the value of count every time
  //the value of 'person' changes
  setCount((count) => count + 1);
}, [person]); //dependency array contains an object as an argument
return (
  <div className="App">
    <p> Value of {count} </p>
  </div>
);

控制台中的结果表明程序无限循环

是什么导致了这个问题?

  • 和之前一样,React 使用浅比较来检查 person 的参考值是否发生了变化
  • 由于 person 对象的参考值在每次渲染时都会发生变化,因此 React 会重新运行 useEffect
  • 因此,这会在每个更新周期调用 setCount。 这意味着我们现在有一个无限循环

如何解决此问题

这就是 useMemo 的用武之地。当依赖关系发生变化时,此 Hook 将计算一个 memoized 值。除此之外,由于我们有一个记忆变量,这确保了状态的参考值在每次渲染期间都不会改变:

//create an object with useMemo
const person = useMemo(
  () => ({ name: "Rue", age: 17 }),
  [] //no dependencies so the value doesn't change
);
useEffect(() => {
  setCount((count) => count + 1);
}, [person]);
传递不正确的依赖项

如果将错误的变量传递给 useEffect 函数,React 将抛出错误,这是一个简短的例子:

const [count, setCount] = useState(0);

useEffect(() => {
  setCount((count) => count + 1);
}, [count]); //notice that we passed count to this array.

return (
  <div className="App">
    <button onClick={() => setCount((count) => count + 1)}>+</button>
    <p> Value of count{count} </p>
  </div>
);

是什么导致了这个问题?

  1. 在上面的代码中,我们告诉在 useEffect 方法中更新 count 的值
  2. 此外,请注意我们也将 count Hook 传递给它的依赖数组
  3. 这意味着每次 count 的值更新时,React 都会调用 useEffect
  4. 结果,useEffect Hook 调用 setCount,从而再次更新 count
  5. 因此,React 现在正在无限循环中运行我们的函数

如何解决此问题

要摆脱无限循环,只需使用一个空的依赖数组,如下所示:

const [count, setCount] = useState(0);
//only update the value of 'count' when component is first mounted
useEffect(() => {
  setCount((count) => count + 1);
}, []);

这将告诉 React 在第一次渲染时运行 useEffect。

总结

尽管 React Hooks 是一个简单的概念,但在将它们合并到您的项目中时需要记住许多规则。这将确保您的应用程序在生产过程中保持稳定、优化并且不会引发错误。

此外,最近发布的 Create React App CLI 还在运行时检测和报告无限循环错误。这有助于开发人员在将这些问题转移到生产服务器之前发现并缓解这些问题。