20

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

我已经尝试过空数组破解,它确实阻止了 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 更改时再次触发。

4

4 回答 4

27

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已更改,从而导致效果的依赖关系不同,从而再次触发该函数-无限。

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

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

这种模式非常常见(且有用),甚至在官方 React Hooks API 文档中也提到过:

如果你想运行一个效果并且只清理一次(在挂载和卸载时),你可以传递一个空数组([])作为第二个参数。这告诉 React 你的效果不依赖于任何来自 props 或 state 的值,所以它永远不需要重新运行。这不是作为特殊情况处理的——它直接遵循依赖项数组的工作方式。

于 2019-09-09T18:40:43.367 回答
3

感谢 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>
  );
};
于 2019-09-09T21:59:33.480 回答
1

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

于 2019-09-09T18:42:07.243 回答
1

我完全同意 Jared 的回答。但是对于某些您真的不想进行参考比较的场景,react-use 库中的 useDeepCompareEffect 非常好 https://github.com/streamich/react-use/blob/HEAD/docs/useDeepCompareEffect.md

于 2021-10-04T05:30:02.450 回答