如何在 HTML 表格中实现跨多行的表头单元格(rowSpan)

本文详解如何使用 `rowspan` 属性让表格左侧的分类标签(如 label 1、label 2)垂直跨多行,避免重复渲染,匹配真实分组数据结构。核心在于预统计每组标签的行数,并在首次出现时设置 `rowspan`,后续同组行跳过该单元格渲染。

在构建分组型表格(如带左侧标签栏的配置表、指标报表或表单摘要)时,常见需求是:同一逻辑分组下的多行数据共享一个左侧标签,而非每行重复显示。HTML 原生支持通过

实现单元格纵向合并,但 React 中需结合数据结构动态计算与条件渲染,否则易出现错位、重复或 rowSpan 值错误等问题。

✅ 正确实现步骤

  1. 预统计每组标签的行数
    使用 reduce 遍历原始数据,按 rowLabel 聚合计数,生成映射对象 numRowsByLabel:

    const numRowsByLabel = data.reduce((acc, row) => {
      acc[row.rowLabel] = (acc[row.rowLabel] || 0) + 1;
      return acc;
    }, {});
  2. 逐标签遍历,内部按数据行渲染
    外层 labels.map() 确保标签顺序可控;内层 data.map() 筛选归属当前标签的行,并仅在该组第一行渲染带 rowSpan 的 ,其余行跳过该列。

  3. 关键逻辑:用状态标记“是否首行”
    注意:React 渲染函数需纯函数式,不可依赖闭包外变量。因此应在每次 label 循环内重置 isFirstRow = true,并在处理每行时更新:

    {labels.map((label) => {
      let isFirstRow = true; // ✅ 每个 label 独立作用域
      return data
        .filter(row => row.rowLabel === label)
        .map((row, idx) => {
          const numRows = numRowsByLabel[label];
          const

    shouldRenderLabelCell = isFirstRow; isFirstRow = false; // ✅ 标记已处理首行 return ( {shouldRenderLabelCell && ( {label} )} {row.key} {row.value} ); }); })}
  4. ⚠️ 注意事项

    • key 必须唯一且稳定:推荐组合 label 与 idx(如 ${label}-${idx}),避免仅用数组索引(ndx),防止重排时 React Diff 异常。
    • rowSpan 值必须准确:若某标签实际对应 3 行,但 rowSpan="3" 却只渲染了 2 行 ,末尾将留空导致布局错乱。务必确保 filter 后的行数与 numRowsByLabel[label] 严格一致。
    • 样式兼容性:避免在 内嵌套 div 或使用 display: flex(会破坏表格盒模型)。所有样式应直接作用于 / ,保持语义化表格结构。
    • 空标签防护:若 labels 数组包含无对应数据的标签,filter(...).length === 0 时需跳过整个 map,避免渲染空 。

      ✅ 最终效果对比

      问题代码(错误) 正确实现(rowSpan)
      每行重复渲染 LABEL 1 → 布局冗余、语义混乱 LABEL 1 单元格 rowSpan="2",覆盖两行高度,视觉与语义统一

      通过以上方法,你不仅能解决当前的渲染问题,还能构建可扩展的分组表格组件——未来支持排序、折叠、动态加载时,只需复用 numRowsByLabel 逻辑即可。记住:rowSpan 不是魔法,而是对数据分组关系的显式声明。