React中用memo优化含array.map的表格tbody,避免不必要重渲染
问题描述
我有一个根据筛选条件展示电影的表格,不希望用户点击复选框时表格tbody每次都重新渲染。以下是我编写的需要被memo化的表格tbody代码:
const Tbody = memo(({ pelis, generos }) => { return pelis.length > 0 && generos.length > 0 ? (<tbody> {pelis.map((p, index) => <tr key={index}> <td>{index}</td> <td><img src={"http://localhost:5000" + p.imgPath} /></td> <td>{p.name}</td> <td>{(new Date(p.date)).toLocaleDateString("es-ES", { day: "numeric", month: "numeric", year: "numeric" })}</td> <td>{(generos.find(g => g._id === p.genre).name)}</td> <td>{p.subgenre && (generos.find(g => g._id === p.subgenre).name) || "-"}</td> </tr>)} </tbody>) : null }, arePropsEqual) function arePropsEqual(oldProps, newProps) { return ( oldProps.pelis.length === newProps.pelis.length ) }
我尝试了两种memo的使用方式,但可能存在疏漏或错误,希望实现tbody仅在电影数据变化时才重新渲染。
问题分析与修复
你的arePropsEqual函数逻辑过于简略,仅对比了pelis的长度,会引发两类问题:
- 电影列表长度不变但内容更新(如单部电影信息修改)时,组件不会重新渲染,显示旧数据
- 筛选条件变化导致
generos更新但pelis长度不变时,组件错误地不重新渲染,而表格依赖generos显示分类名称,会出现信息不匹配
另外,用index作为<tr>的key,在列表顺序变化时会导致React复用错误DOM节点,引发不必要的渲染或显示异常,建议改用电影的唯一标识(如p._id)作为key。
修复后的代码
// 对比电影数组内容是否一致 const isPelisEqual = (oldPelis, newPelis) => { if (oldPelis.length !== newPelis.length) return false; // 对比关键字段,避免全属性深层遍历 return oldPelis.every((oldPeli, index) => { const newPeli = newPelis[index]; return ( oldPeli._id === newPeli._id && oldPeli.name === newPeli.name && oldPeli.date === newPeli.date && oldPeli.genre === newPeli.genre && oldPeli.subgenre === newPeli.subgenre && oldPeli.imgPath === newPeli.imgPath ); }); }; // 对比分类数组内容是否一致 const isGenerosEqual = (oldGeneros, newGeneros) => { if (oldGeneros.length !== newGeneros.length) return false; return oldGeneros.every((oldGenre, index) => { const newGenre = newGeneros[index]; return oldGenre._id === newGenre._id && oldGenre.name === newGenre.name; }); }; const Tbody = memo(({ pelis, generos }) => { return pelis.length > 0 && generos.length > 0 ? ( <tbody> {pelis.map((p) => ( <tr key={p._id}> {/* 改用电影唯一ID作为key */} <td>{pelis.indexOf(p)}</td> {/* 基于数据获取序号,避免index作为key的问题 */} <td><img src={`http://localhost:5000${p.imgPath}`} /></td> <td>{p.name}</td> <td> {new Date(p.date).toLocaleDateString("es-ES", { day: "numeric", month: "numeric", year: "numeric", })} </td> <td>{generos.find(g => g._id === p.genre).name}</td> <td>{p.subgenre ? generos.find(g => g._id === p.subgenre).name : "-"}</td> </tr> ))} </tbody> ) : null; }, (oldProps, newProps) => { // 仅当电影数组和分类数组都无变化时,跳过重新渲染 return isPelisEqual(oldProps.pelis, newProps.pelis) && isGenerosEqual(oldProps.generos, newProps.generos); });
补充说明
- 如果
pelis和generos是后端返回的不可变数据(每次更新都是新数组+新对象),可以用lodash.isEqual简化对比,但注意大量数据下的性能开销 - 序号用
pelis.indexOf(p)有轻微性能损耗,若序号非必需可直接移除,或在数据预处理阶段给每个电影添加序号字段 - 将
p.subgenre && ... || "-"改为p.subgenre ? ... : "-",逻辑更清晰,避免布尔值判断的潜在问题
内容的提问来源于stack exchange,提问作者Kevin
相关产品推荐
相关产品推荐

