【发布时间】:2021-01-28 07:33:05
【问题描述】:
将 React 钩子与子组件一起使用,该子组件应从父组件获取初始状态,并在每次内部状态更改时更新父组件。
我认为,由于它始终是相同的引用,因此不应无限调用子级的 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>
);
}
【问题讨论】:
-
如果不深入研究,我认为这是由第二个
useEffect[data, onChange]的依赖数组引起的。每次父组件重新渲染时,子组件中的 onChange 将是一个新的参考。由于any call to setState 导致重新渲染,您创建了一个循环,即useEffect调用父级中的setState,这将创建一个新函数,该函数重新运行useEffect。 -
感谢@JacobSmit 的输入。根据您的评论,我已将 useCallback 添加到我的问题的代码 sn-p 中(尽管我在沙箱中使用了 useCallback)。然而,这并不能解决问题:(
-
onChange 是一个明显的问题,但不是唯一的问题。我没有机会调试,但我想问题是不同的初始子状态使两个具有不同输入的 useEffect 争夺状态并在 {} 和 {counter:4} 之间无限交换。他们应该以某种方式返工。初始状态和当前状态之间应该有明显的区别,并且应该有一个单一的事实来源。如果您不想像答案建议的那样将状态提升给父母,至少不要从孩子那里更新父母的初始状态。父母甚至需要知道孩子的当前状态吗?
-
感谢@EstusFlask。我不会更新父母的初始状态,但是当孩子的状态发生变化时会经常更新它。在我的真实项目中,孩子需要更新父级,以便它可以更新不同的兄弟姐妹,这就是为什么我将更改传递给父级,以便他可以将其传递给另一个兄弟姐妹。
-
嗯,你这样做是因为当你更新父状态时,你更新了初始数据。根据你的描述,这是一个完全有效的提升国家的案例。如果有 3 个孩子,那么父母将处理 3 个状态。
标签: javascript reactjs react-hooks infinite-loop