将 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>
);
}
问题在于 JS 中
{} !== {}
,因为对象与原始值不同,是通过引用而不是值进行比较的。
在你
useEffect
中,你正在比较两个对象,因为它们总是有不同的引用,在JS领域它们永远不会相同,你的useEffect
将触发,设置新对象,你自己就会陷入无限循环。
你不应该像在 React 中使用类组件一样使用钩子,这意味着你应该这样做
const [counter, setCounter] = useState(4);
这样,您将把原始值传递给您的子组件,并且
useEffect
将具有更可预测的行为。
此外,虽然这是一个测试用例,但您应该很少(阅读:从不)尝试将子状态设置为父状态。您已经将该数据从父组件传递给子组件,无需在子组件中创建冗余状态,只需使用传入的数据即可。
如何将状态 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>
关于解决方案,我建议您不要在子组件中设置任何初始状态(或将其设置为空对象
{}
)。第一个 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>;
};
对于其他评论,我同意,而是将状态从父母传递给孩子。
如果使用空对象作为依赖项的 useEffect 导致无限循环,您应该使用 JSON.stringify(data) 包装该对象。这最近发生在我身上并解决了所有问题
useEffect(() => {
setData(initialData);
}, [JSON.stringify(initialData)]);