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

时间:2019-09-09 18:33:39

标签: reactjs react-hooks use-effect

我有利用钩子的功能组件。在 useEffect 钩子中,我只想从后端获取数据并将结果存储为state。但是,尽管将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更改时才触发。

3 个答案:

答案 0 :(得分:2)

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

一种简单的解决方案是简单地从依赖项数组中删除invoiceData。这样,useEffect函数的行为基本上与componentDidMount相似,因为它将在组件首次呈现时触发一次,并且仅触发一次。

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

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

  

如果您要运行效果并仅将其清理一次(在挂载和   卸载),则可以传递一个空数组([])作为第二个参数。这个   告诉React你的效果不依赖于道具的任何值   或状态,因此它不需要重新运行。这不是特殊处理   case —直接从依赖关系数组始终如何得出   有效。

答案 1 :(得分:1)

正在发生的事情是,当您更新invoiceData时,从技术上改变了invoiceData的状态,您已经用useEffect挂钩监视了该状态,这导致挂钩再次运行,它更新invoiceData。如果您怀疑useEffect在挂载上运行,请将空数组传递给useEffect的第二个参数,该参数在类组件中模拟componentDidMount。然后,您将能够使用useState钩子更新本地UI状态。

答案 2 :(得分:1)

对伟大的“幕后”解释大加赞赏;我还发现Milind提出的将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>
  );
};