You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的长度,会引发两类问题:

  1. 电影列表长度不变但内容更新(如单部电影信息修改)时,组件不会重新渲染,显示旧数据
  2. 筛选条件变化导致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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 00:54:55