将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>
);
}
答案 0 :(得分:2)
如何将状态 only 放在父组件中,让孩子仅引用传递给它的道具,而没有其自身的任何状态?
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 :(得分:2)
问题在于JS {} !== {}
中的问题,因为与原始值不同,对象是通过引用而不是值进行比较的。
在您useEffect
中,您正在比较2个对象,因为它们始终具有不同的引用,因此在JS领域中它们永远不会相同,并且您的useEffect
将触发并设置新对象,自己陷入无限循环。
您不应像在react中使用类组件那样使用钩子,这意味着您应该这样做
const [counter, setCounter] = useState(4);
这样,您会将原始值传递给子组件,useEffect
的行为将更加可预测。
此外,尽管这是一个测试用例,但您应该极少(请参阅:从不)尝试将子状态设置为父状态。您已经将数据从父级传递给子级,无需在子级组件中创建冗余状态,只需使用传入的数据即可。
答案 2 :(得分: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>;
};
我同意其他评论,而不是将州由父母传给孩子。