使用空对象反应 useState 会导致无限循环

问题描述 投票:0回答:4

将 React hook 与子组件一起使用,子组件应该从父组件获取初始状态,并在每次内部状态更改时更新父组件。
我认为由于它始终是相同的引用,因此子级的 useEffect 不应该被无限调用。

如果子对象的初始状态是一个空对象,我会得到一个无限循环。
如果孩子的初始状态是从道具中获取的,那么效果很好。

不确定是什么原因造成的。
您可以将子组件内的第一个 useState 更改为空对象,以开始无限循环。

请查看下面的沙箱:
https://codesandbox.io/s/weird-initial-state-xi5iy?fontsize=14&hidenavigation=1&theme=dark
注意:我在沙箱中添加了一个计数器,以便在运行 10 次后停止循环,并且不会导致浏览器崩溃。

import React, { useState, useEffect, useCallback } from "react";

const problematicInitialState = {};

/* CHILD COMPONENT */
const Child = ({ onChange, initialData }) => {
  const [data, setData] = useState(initialData); // if initialData is {} (a.k.a problematicInitialState const) we have an infinite loop

  useEffect(() => {
    setData(initialData);
  }, [initialData]);

  useEffect(() => {
    onChange(data);
  }, [data, onChange]);

  return <div>Counter is: {data.counter}</div>;
};

/* PARENT COMPONENT */
export default function App() {
  const [counterData, setCounterData] = useState({ counter: 4 });

  const onChildChange = useCallback(
    (data) => {
      setCounterData(data);
    },
    [setCounterData]
  );

  return (
    <div className="App">
      <Child onChange={onChildChange} initialData={counterData} />
    </div>
  );
}
javascript reactjs react-hooks infinite-loop
4个回答
3
投票

问题在于 JS 中

{} !== {}
,因为对象与原始值不同,是通过引用而不是值进行比较的。

在你

useEffect
中,你正在比较两个对象,因为它们总是有不同的引用,在JS领域它们永远不会相同,你的
useEffect
将触发,设置新对象,你自己就会陷入无限循环。

你不应该像在 React 中使用类组件一样使用钩子,这意味着你应该这样做

const [counter, setCounter] = useState(4);

这样,您将把原始值传递给您的子组件,并且

useEffect
将具有更可预测的行为。

此外,虽然这是一个测试用例,但您应该很少(阅读:从不)尝试将子状态设置为父状态。您已经将该数据从父组件传递给子组件,无需在子组件中创建冗余状态,只需使用传入的数据即可。


2
投票

如何将状态 only 放在父组件中,让子组件仅引用传递给它的 props,而不拥有自己的任何状态?

const Child = ({ counterData, setCounterData }) => {
  return (
    <div>
      <div>Counter is: {counterData.counter}</div>
      <button
        onClick={() => setCounterData({ counter: counterData.counter + 1 })}
      >increment</button>
    </div>
  );
};

const App = () => {
  const [counterData, setCounterData] = React.useState({ counter: 4 });
  return (
    <div className="App">
      <Child {...{ counterData, setCounterData }} />
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class="react"></div>


1
投票

关于解决方案,我建议您不要在子组件中设置任何初始状态(或将其设置为空对象

{}
)。第一个
useEffect
将处理第一次更新。

const Child = ({ onChange, initialData }) => {
  const [data, setData] = useState({});

  useEffect(() => {
    setData(initialData);
  }, [initialData]);

  useEffect(() => {
    onChange(data);
  }, [data, onChange]);

  return <div>Counter is: {data.counter}</div>;
};

对于其他评论,我同意,而是将状态从父母传递给孩子。


0
投票

如果使用空对象作为依赖项的 useEffect 导致无限循环,您应该使用 JSON.stringify(data) 包装该对象。这最近发生在我身上并解决了所有问题

     useEffect(() => {
    setData(initialData);
  }, [JSON.stringify(initialData)]);
© www.soinside.com 2019 - 2024. All rights reserved.