如何在更新父组件对象数组状态时仅重渲染目标ItemCard组件?
React.memo缓存组件后仍全部重渲染的解决办法
问题场景
我尝试用memo缓存ItemCard组件,期望调用setItems()更新父组件ItemList的对象数组状态时,仅被修改的ItemCard触发重渲染,但点击按钮后所有ItemCard均出现了重渲染。
父组件ItemList代码:
const ItemList = () => { const [items, setItems] = useState([ { id: 1, count: 5 }, { id: 2, count: 7 }, { id: 3, count: 2 }, ]); const updateItemCount = (itemId, add) => { const updatedItems = items.map((item) => { const { id, count } = item; if (id === itemId) { return { ...item, count: add ? count + 1 : count - 1 }; } return item; }); setItems(updatedItems); }; return ( <div> {items.map((item) => ( <ItemCard {...item} updateItemCount={updateItemCount} /> ))} </div> ); };
子组件ItemCard代码:
import { memo } from 'react'; const ItemCard = memo(({id, count, updateItemCount}) => { console.log("re-rendered", id) return ( <button onClick={() => updateItemCount(id, true)}></button> ); });
问题原因
核心问题在于updateItemCount函数:每次父组件ItemList重渲染时,这个函数都会被重新创建,导致传递给每个ItemCard的updateItemCount prop都是全新的引用。memo默认采用浅比较props,只要有一个prop的引用发生变化,就会判定为props更新,触发子组件重渲染。
即便只有一个item对象被修改,其他未修改的item引用没有变化,但因为updateItemCount的引用变了,所有ItemCard都会触发重渲染。
解决方案
1. 用useCallback缓存updateItemCount函数
使用useCallback包裹函数,同时采用函数式更新state,避免依赖外部的items变量,确保函数引用稳定。
修改后的父组件代码:
import { useState, useCallback } from 'react'; const ItemList = () => { const [items, setItems] = useState([ { id: 1, count: 5 }, { id: 2, count: 7 }, { id: 3, count: 2 }, ]); // 用useCallback缓存函数,函数式更新state避免依赖外部items const updateItemCount = useCallback((itemId, add) => { setItems(prevItems => prevItems.map(item => { if (item.id === itemId) { return { ...item, count: add ? item.count + 1 : item.count - 1 }; } return item; })); }, []); // 依赖项为空,setItems是React提供的稳定引用 return ( <div> {items.map((item) => ( {/* 必须添加key属性,这是React列表渲染的规范 */} <ItemCard key={item.id} {...item} updateItemCount={updateItemCount} /> ))} </div> ); };
2. 保留memo的浅比较逻辑
子组件的memo无需额外修改,现在updateItemCount引用稳定后,未修改的ItemCard的所有props(id、count、updateItemCount)都与上一次渲染一致,memo会判定props未变化,跳过重渲染。
优化后的子组件(可补充按钮文本方便测试):
import { memo } from 'react'; const ItemCard = memo(({ id, count, updateItemCount }) => { console.log("re-rendered", id); return ( <button onClick={() => updateItemCount(id, true)}> 数量:{count} +1 </button> ); }); export default ItemCard;
效果验证
修改完成后,点击任意ItemCard的按钮,只有该组件的控制台会打印re-rendered日志,其他未修改的组件不会触发重渲染,达到预期效果。
内容的提问来源于stack exchange,提问作者Grambam
相关产品推荐
相关产品推荐

