🌲 系列一:跟着官方示例学习 @tanStack-table --- Basic
在上一篇中,我们已经愉快地搭好了一个基本的表格。这次我们来给表格加点"分组逻辑",让表头也学会团队合作,一起登场!
没错,我们今天的主题是:🧩 Header Groups(表头分组)
✨ 为什么需要表头分组?
当你有很多列,或者希望某几列逻辑上属于同一类时,"分组表头"就变得超级有用了。
比如我们希望把 firstName
和 lastName
归为一组叫 Name
,把 age
、visits
、status
、progress
归为另一组叫 Info
,那就可以用 分组列配置 来实现。
🔔 对官方示例代码可能存在一些删减的情况
代码链接🔗:tanstack.com/table/lates...
tsx
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()
创建分组:
tsx
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>
里遍历的是:
tsx
table.getHeaderGroups().map((headerGroup) => (
<tr>
{headerGroup.headers.map((header) => (
<th colSpan={header.colSpan}>
{flexRender(...)}
</th>
))}
</tr>
))
注意两点:
getHeaderGroups()
会自动根据你的嵌套分组列生成多层表头;
每个 header
带有 colSpan
属性,表示它横跨几列 ------ 必须用于 <th colSpan={...}>
才能对齐!
📊 最终效果是?

你可以把它理解为一个二维表头。更清晰、分组明确,是不是感觉更"专业表格"了!
但是看这个表格,有些表头是为了布局而存在的"占位单元格"(placeholder cells),它们不包含内容,就是为了撑开结构用的。
header
设置了 isPlaceholder: true
,你就可以根据它是否是"空单元格"来决定 要不要渲染内容。
🪐 isplaceholder
tsx
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
