问题描述
当嵌套太深的子组件触发更新父组件时,父组件获取到的state(map传入子组件)是旧的
问题场景
初始子组件仅为1个Input输入框:
新增后有2个Input输入框:
此时触发222输入框的修改,通知上级组件保存修改的内容时,父组件存储两个Input输入框的State:list,打印出来是旧的!
触发444输入框的修改,打印list:
具体看源码如下:
问题代码
父组件
// 父组件
const Parent = () => {
const [list, setList] = useState([]);
//模拟拉取数据
useEffect(() => {
setList([
{
name: 111,
subList: [
{
value: 222,
},
],
},
]);
}, []);
const onChildChange = (value) => {
console.log('子组件触发更新,此时的list:', list);
};
const handleAddList = () => {
const newList = [
...list,
{
name: 333,
subList: [
{
value: 444,
},
],
},
];
setList(newList);
};
return (
<>
{list.length > 0 && (
list.map(item => (
<Children key={item.name} value={item.subList} onChange={onChildChange} />
))
)}
<Button onClick={handleAddList}>新增</Button>
</>
)
}
子组件
const Children = ({
value,
onChange,
}) => {
const [subList, setSubList] = useState([]);
useEffect(() => {
setSubList([...value]);
}, [value]);
const handleChange = (e) => {
onChange(e.target.value);
};
const columns = useMemo(() => [
{
render: (text: string, item, index) => (
<Input value={item.value} onChange={handleChange} />
),
},
], [subList]);
return (
<>
{subList.length > 0 && (
<Table
rowKey="value"
columns={columns}
showHeader={false}
dataSource={subList}
className={s.mountsWrapper}
pagination={false}
/>
)}
</>
);
};
问题分析
由于子组件又使用Table渲染导致嵌套过深,传入子组件的props:
<Children value={item.subList} onChange={onChildChange} />
item是list的子项,子组件仅收到item.subList的prop,当list新增第二个输入框时,子组件调用onChange的箭头函数内,上下文查到的list是旧的。
解决办法1
props传参尽量选择传入state,比如这里的list:
<Children parentList={list} pIndex={index} onChange={onChildChange} />
只需要继续在子组件内通过pIndex即可找到原本需要传入的item
当父级list变化时,子组件也能及时更新触发onChange时的list
解决办法2
不使用Table渲染子组件数据(目测正常,但是原理不能弄明白)
const Children = ({
value,
onChange,
}) => {
const [subList, setSubList] = useState([]);
useEffect(() => {
setSubList([...value]);
}, [value]);
const handleChange = (e) => {
onChange(e.target.value);
};
return (
<>
{subList.length > 0 && (
subList.map(item => (
<Input key={item.value} value={item.value} onChange={handleChange} />
))
)}
</>
);
};