React memo和/或useCallback没有按预期工作



所以我有这个Home Component,其中有一个records状态,我用它来执行records.map()并返回表内的RecordItem组件。

function Home() {
const [records, setRecords] = useState<Array<RecordType>>(loadRecords());
const removeRecord = useCallback((record: RecordType) => {
setRecords(records => {
const newRecords = records.filter(rec => rec !== record);
localStorage.setItem('controle_financeiro', JSON.stringify(newRecords));
return newRecords;
});
}, []);
return (
<tbody>
{
records.map((record, index) => <RecordItem key={index} record={record} removeRecord={removeRecord}/>)
}
</tbody>
)
}

RecordItem组分,被Home组分使用。如您所见,有一个按钮,在单击时执行removeRecord函数。该函数作为props传递,因此在Home组件中,它是一个记忆函数。

function RecordItem({record, removeRecord}: RecordProps) {
console.log('Renderized RecordItem'); // if there is 50 items in the `records` state array, it will print 50x

return (
<tr>
<td>{record.description}</td>
<td>{record.value}</td>
<td>{record.type === 'in' ? <FaRegArrowAltCircleUp className='in-icon'/> : <FaRegArrowAltCircleDown className='out-icon'/>}</td>
// button that executes the removeRecord function
<td> <FaTrash className='remove-icon' onClick={e => removeRecord(record)}/> </td>
</tr>
)
};

export default memo(RecordItem);

因此,如果records状态中有50个项目,在Home组件中,它将在控制台中打印Renderized RecordItem50x。它再次渲染整个数组,我不想要它。我做错什么了吗?

key={index}

问题是你正在使用索引作为键。

假设您删除了数组中的第一项。您期望反应从页面中删除第一项,其余的保持不变,但是键要求做其他事情。在一次渲染中,有一个包含50个项目的列表,键值为0-49,然后在下一次渲染中,有一个包含49个项目的列表,键值为0-48。基于此,react将删除最后一个元素,因为只有这个元素的键不再存在。对于所有其他键,道具都发生了变化:键0正在获取曾经在键1中的记录,键1正在获取来自键2的记录,等等。因此,所有这些组件都收到了新的道具,打破了它们的备忘录。

要解决这个问题,您需要在记录上使用一些唯一标识符,并将其用作键。如果你已经有了RecordType的值,使用它。如果没有,则需要添加一个。也许loadRecords函数可以做到这一点。

{records.map((record, index) => <RecordItem key={index} record={record} removeRecord={removeRecord}/>)}

不要使用index作为键。在这种情况下,如果通过单击按钮删除一个项目,则所有项目都将重新呈现。因为索引改变了。所以必须使用其他唯一数据作为键。是否record有一个唯一的id或其他东西?