我正在与React.memo一起使用React功能组件,但是我遇到了一个问题,我认为这是由JavaScript闭包引起的。 (我还将Immer用于不变性,但我相信它不会影响这种情况。)
下面是这种情况的简化版本:
import React, { useState } from 'react';
import produce from "immer";
const initialData = [
{id: 1, value: 0},
{id: 2, value: 0},
{id: 3, value: 0}
]
const ChildComponent = memo(
({ value, onChange }) => (
<p>
<input value={value} onChange={onChange} type="number" />
</p>
),
(prevProps, nextProps) => prevProps.value === nextProps.value
);
const ParentComponent = props => {
const [data, setData] = useState(initialData);
const onDataChange = id => event => {
setData(
produce(data, draft => {
const record = draft.find(entry => entry.id === id);
record.value = event.target.value;
})
);
};
return data.map(record => (
<ChildComponent
key={record.id}
value={record.value}
onChange={onDataChange(record.id)}
/>
));
};
我正在使用React.memo避免不必要地重新渲染其值不变的ChildComponents,但是通过这种方式,ChildComponent存储了onChange函数的旧版本,该版本(据我的理解是由于JavaScript闭包)引用了数据的旧版本。
这样做的结果是,当我最初更改第一个ChildComponent的值,然后再更改另一个ChildComponent的值时,第一个ChildComponent的值恢复为初始值。
可以在this sandbox中找到情况的再现。
在网上搜索后,我发现此问题的解决方案是在onChange函数中使用ref来获取最新数据,如下所示:
const ParentComponent = props => {
const [data, setData] = useState(initialData);
const dataRef = useRef();
dataRef.current = data;
const onDataChange = id => event => {
setData(
produce(dataRef.current, draft => {
const record = draft.find(entry => entry.id === id);
record.value = event.target.value;
})
);
};
return data.map(record => (
<ChildComponent
key={record.id}
value={record.value}
onChange={onDataChange(record.id)}
/>
));
};
可以找到具有此解决方案的沙箱here。
这解决了问题。但是我想问,这是正确的解决方案吗?还是我使用React和React Hooks的方式错误?
答案 0 :(得分:1)
尽管使用引用是有效的解决方案,但您应该使用functional setState
。
如果使用先前状态计算新状态,则可以将函数传递给setState。该函数将接收先前的值,并返回更新的值。
const onDataChange = id => event => {
event.persist();
setData(data =>
produce(data, draft => {
const record = draft.find(entry => entry.id === id);
record.value = event.target.value;
})
);
};