尽管依赖项没有变化,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 警告以放置缺少的依赖项。但是钩子中的依赖值发生了变化