尽管依赖项没有变化,useEffect 运行无限循环

Posted

技术标签:

【中文标题】尽管依赖项没有变化,useEffect 运行无限循环【英文标题】:useEffect runs infinite loop despite no change in dependencies 【发布时间】:2020-01-11 13:02:56 【问题描述】:

我有使用钩子的函数组件。在 useEffect 钩子中,我只想从后端获取数据并将结果存储在状态中。然而,尽管添加了 data 变量作为依赖项,useEffect 仍然会在无限循环中触发 - 即使数据没有更改。如何阻止 useEffect 连续触发?

我尝试了空数组 hack,它确实阻止了 useEffect 连续触发,但这不是所需的行为。例如,如果用户保存新数据,useEffect 应该再次触发以获取更新的数据 - 我不想模拟 componentDidMount。

const Invoices = () => 
  const [invoiceData, setInvoiceData] = useState([]);

  useEffect(() => 
    const updateInvoiceData = async () => 
      const results = await api.invoice.findData();
      setInvoiceData(results);
    ;
    updateInvoiceData();
  , [invoiceData]);

  return (
    <Table entries=invoiceData />
  );
;

我希望 useEffect 在初始渲染后触发,并且仅在 invoiceData 更改时再次触发。

【问题讨论】:

看看我这里的解释***.com/questions/57847626/… 这不是和空依赖数组一样的行为吗? if (!invoiceData) updateInvoiceData(); 如果invoiceData 中有一些值,它将继续被调用。 useEffect 中的第二个参数会更好地处理这个问题,并且只会在 invoiceData 中的值更改时执行代码。但是,如果每次您拨打电话时都会更改该值,那么这将不起作用。 我建议将updateInvoiceData逻辑移动到单独的方法中,并且仅在第一次使用useEffect,然后每当保存新数据时,再次调用该方法(不要使用useEffect ) @Trevor 你是怎么解决这个问题的?我有类似的问题。投票最多的答案指出了问题,但没有提供解决方案。 【参考方案1】:

useEffect 依赖数组的工作方式是检查数组中来自先​​前渲染和新渲染的所有项之间的严格 (===) 等效性。因此,将一个数组放入您的 useEffect 依赖数组是非常麻烦的,因为与 === 的数组比较检查的等价性通过引用而不是通过内容。

const foo = [1, 2, 3];
const bar = foo;
foo === bar; // true

const foo = [1, 2, 3];
const bar = [1, 2, 3];
foo === bar; // false

在效果函数内部,当您执行setInvoiceData(results) 时,您将invoiceData 更新为一个新数组。即使该新数组中的所有项目完全相同,对新invoiceData 数组的reference 已更改,导致效果的依赖关系不同,再次触发该函数--ad无限。

一个简单的解决方案是从依赖数组中删除invoiceData。这样,useEffect 函数的作用与 componentDidMount 基本类似,它会在组件第一次渲染时触发一次且仅触发一次。

useEffect(() => 
    const updateInvoiceData = async () => 
      const results = await api.invoice.findData();
      setInvoiceData(results);
    ;
    updateInvoiceData();
  , []);

这种模式非常常见(且有用),甚至在the official React Hooks API documentation 中提到:

如果你想运行一个效果并且只清理一次(在挂载和 unmount),您可以传递一个空数组 ([]) 作为第二个参数。这 告诉 React 你的效果不依赖于 props 的任何值 或状态,因此它永远不需要重新运行。这不作为特殊处理 case — 它直接遵循依赖项数组的方式 有效。

【讨论】:

这在详尽的 deps 被关闭但在需要时有效? @Ryan // eslint-disable-next-line react-hooks/exhaustive-deps 给bug留了空间,难道没有更好的解决方案吗?我见过一些实现 useRefs。 @Ryan 你是什么意思?最终由程序员来理解 useEffect 和依赖数组如何工作并正确使用它们。 eslint 或任何其他静态分析工具无法“知道”您是否打算让 useEffect 挂钩像“componentDidMount”一样工作并相应地进行 lint。如果您担心删除太多 eslint-disable cmets,您可以为自己编写一个 useOnMount() 自定义钩子,它简单地包装所需的行为(空 deps 数组、清理和 eslint 注释)。【参考方案2】:

感谢 jered 出色的“幕后”解释;我还发现 Milind 将 update 方法从 useEffect 中分离出来的建议特别有成效。 为简洁起见,我的解决方案如下 -

const Invoices = () => 
  const [invoiceData, setInvoiceData] = useState([]);

  useEffect(() =>     
    updateInvoiceData();
  , []);

  // Extracting this method made it accessible for context/prop-drilling
  const updateInvoiceData = async () => 
    const results = await api.invoice.findData();
    setInvoiceData(results);
  ;

  return (
    <div>
      <OtherComponentThatUpdatesData handleUpdateState=updateInvoiceData />
      <Table entries=invoiceData />
    </div>
  );
;

【讨论】:

【参考方案3】:

发生的情况是,当您更新 invoiceData 时,从技术上讲,这会更改 invoiceData 的状态,您已通过 useEffect 钩子观察到,这会导致钩子再次运行,从而更新 invoiceData .如果您希望useEffect 在mount 上运行,我怀疑,然后将一个空数组传递给useEffect 的第二个参数,它在类组件中模拟componentDidMount。然后,您将能够使用 useState 挂钩更新本地 UI 状态。

【讨论】:

+1。这将是一个很好的例子,当我们可以用[]“伪造”deps,因为更新invoiceData会触发重新渲染,运行效果,然后再次设置invoiceData,等等......【参考方案4】:

我完全同意 Jared 的回答。 但是对于一些你真的不想有参考比较的场景,那么 react-use 库中的 useDeepCompareEffect 真的很好 https://github.com/streamich/react-use/blob/HEAD/docs/useDeepCompareEffect.md

【讨论】:

以上是关于尽管依赖项没有变化,useEffect 运行无限循环的主要内容,如果未能解决你的问题,请参考以下文章

如何避免“React Hook useEffect 缺少依赖项”和无限循环

ESLint 希望 setSate 作为 useEffect 的依赖项,但这会导致无限循环(react-hooks/exhaustive-deps)

反应 useEffect 警告以放置缺少的依赖项。但是钩子中的依赖值发生了变化

ReactNative:超过最大更新深度错误

React Hook useEffect 缺少依赖项:'execute'

当所有依赖项都发生变化时使用效果?