跟着官方示例学习 @tanStack-table --- Header Groups

78 阅读2分钟

🌲 系列一:跟着官方示例学习 @tanStack-table --- Basic


在上一篇中,我们已经愉快地搭好了一个基本的表格。这次我们来给表格加点“分组逻辑”,让表头也学会团队合作,一起登场!

没错,我们今天的主题是:🧩 Header Groups(表头分组)

✨ 为什么需要表头分组?

当你有很多列,或者希望某几列逻辑上属于同一类时,“分组表头”就变得超级有用了。

比如我们希望把 firstNamelastName 归为一组叫 Name,把 agevisitsstatusprogress 归为另一组叫 Info,那就可以用 分组列配置 来实现。

🔔 对官方示例代码可能存在一些删减的情况

代码链接🔗:tanstack.com/table/lates…

type Person = {
  firstName: string
  lastName: string
  age: number
  visits: number
  status: string
  progress: number
}

const defaultData: Person[] = [
  {
    firstName: 'tanner',
    lastName: 'linsley',
    age: 24,
    visits: 100,
    status: 'In Relationship',
    progress: 50,
  },
  {
    firstName: 'tandy',
    lastName: 'miller',
    age: 40,
    visits: 40,
    status: 'Single',
    progress: 80,
  },
  {
    firstName: 'joe',
    lastName: 'dirte',
    age: 45,
    visits: 20,
    status: 'Complicated',
    progress: 10,
  },
]

const columnHelper = createColumnHelper<Person>()

const columns = [
  columnHelper.group({
    id: 'name',
    header: () => <span>Name</span>,
    columns: [
      columnHelper.accessor('firstName', {
        cell: info => info.getValue(),
        footer: props => props.column.id,
      }),
      columnHelper.accessor(row => row.lastName, {
        id: 'lastName',
        cell: info => info.getValue(),
        header: () => <span>Last Name</span>,
        footer: props => props.column.id,
      }),
    ],
  }),
  columnHelper.group({
    header: 'Info',
    footer: props => props.column.id,
    columns: [
      columnHelper.accessor('age', {
        header: () => 'Age',
        footer: props => props.column.id,
      }),
      columnHelper.group({
        header: 'More Info',
        columns: [
          columnHelper.accessor('visits', {
            header: () => <span>Visits</span>,
            footer: props => props.column.id,
          }),
          columnHelper.accessor('status', {
            header: 'Status',
            footer: props => props.column.id,
          }),
          columnHelper.accessor('progress', {
            header: 'Profile Progress',
            footer: props => props.column.id,
          }),
        ],
      }),
    ],
  }),
]

function App() {
  const table = useReactTable({
    data: defaultData,
    columns,
    getCoreRowModel: getCoreRowModel(),
  })

  return (
    <div className="p-2">
      <table>
        <thead>
          {table.getHeaderGroups().map(headerGroup => (
            <tr key={headerGroup.id}>
              {headerGroup.headers.map(header => (
                <th key={header.id} colSpan={header.colSpan}>
                  {flexRender(header.column.columnDef.header, header.getContext())}
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody>
          {table.getRowModel().rows.map(row => (
            <tr key={row.id}>
              {row.getVisibleCells().map(cell => (
                <td key={cell.id}>
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
        <tfoot>
          {table.getFooterGroups().map(footerGroup => (
            <tr key={footerGroup.id}>
              {footerGroup.headers.map(header => (
                <th key={header.id} colSpan={header.colSpan}>
                  {flexRender(header.column.columnDef.footer, header.getContext())}
                </th>
              ))}
            </tr>
          ))}
        </tfoot>
      </table>
    </div>
  )
}

🛠 怎么做?

我们不再直接用 accessor 一列一列堆叠了,而是用 columnHelper.group() 创建分组:

const columns = [
  columnHelper.group({
    id: 'hello',
    header: () => <span>Hello</span>,
    columns: [
      columnHelper.accessor('firstName', { ... }),
      columnHelper.accessor(row => row.lastName, { ... }),
    ],
  }),
  columnHelper.group({
    header: 'Info',
    columns: [
      columnHelper.accessor('age', { ... }),
      columnHelper.group({
        header: 'More Info',
        columns: [
          columnHelper.accessor('visits', { ... }),
          columnHelper.accessor('status', { ... }),
          columnHelper.accessor('progress', { ... }),
        ],
      }),
    ],
  }),
]

是的,你没有看错,分组是可以嵌套的,像“套娃”一样!👶🧒👨

🧠 group() 的几个关键点

参数说明
id分组的唯一标识(不是必须的,但加了会更清晰)
header分组的表头内容,可以是字符串或函数返回 React 元素
columns这个分组下的子列或子分组,数组形式

每个 group()accessor() 都是“列对象”,可以嵌套组成一棵列树 🌲。

📦 渲染时发生了什么?

我们之前在 <thead> 里遍历的是:

table.getHeaderGroups().map((headerGroup) => (
  <tr>
    {headerGroup.headers.map((header) => (
      <th colSpan={header.colSpan}>
        {flexRender(...)}
      </th>
    ))}
  </tr>
))

注意两点:

getHeaderGroups() 会自动根据你的嵌套分组列生成多层表头;

每个 header 带有 colSpan 属性,表示它横跨几列 —— 必须用于 <th colSpan={...}> 才能对齐!

📊 最终效果是?

image.png

你可以把它理解为一个二维表头。更清晰、分组明确,是不是感觉更“专业表格”了!

但是看这个表格,有些表头是为了布局而存在的“占位单元格”(placeholder cells),它们不包含内容,就是为了撑开结构用的。

header 设置了 isPlaceholder: true,你就可以根据它是否是“空单元格”来决定 要不要渲染内容。

🪐 isplaceholder

Isplaceholder API 🔗

该属性会返回一个布尔值,表示该头部是否为占位头部

官方文档中提到了与 placeholder 有关的还有一个 placeholderId api(暂时没有想到使用的场景)

<th key={header.id} colSpan={header.colSpan}>
  {header.isPlaceholder
    ? null
    : flexRender(
        header.column.columnDef.header,
        header.getContext()
      )}
</th>

Snipaste_2025-06-06_14-59-16.png