如何在TanStack Table中自定义分组列与分组行渲染样式
问题描述
我需要把现有预算项目表格按年份分组,每个年份的分组行要显示年份及对应项目的金额总和,同时希望分组行和详情行的渲染样式不同,以区分层级。以下是已实现的最小复现代码:
import { useState } from "react"; import { ColumnDef, useReactTable, getCoreRowModel, flexRender, getGroupedRowModel, getExpandedRowModel, } from "@tanstack/react-table"; type Asset = { name: string; year: number; amount: number; }; export function App() { const columns: ColumnDef<Asset>[] = [ { accessorKey: "name", header: "Name", }, { accessorKey: "year", header: "Year", }, { accessorKey: "amount", header: "Amount", aggregationFn: "sum", }, ]; const [data, setData] = useState([ { name: "Item 1", year: 2022, amount: 31 }, { name: "Item 2", year: 2022, amount: 4312 }, { name: "Item 3", year: 2022, amount: 1233 }, { name: "Item 4", year: 2024, amount: 123 }, { name: "Item 5", year: 2024, amount: 512 }, ]); const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), getGroupedRowModel: getGroupedRowModel(), getExpandedRowModel: getExpandedRowModel(), state: { grouping: ["year"], }, }); return ( <table> <thead> {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : 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}> {cell.getIsPlaceholder() ? null : flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ))} </tbody> </table> ); }
解决方案
完全可以实现,核心是利用TanStack React Table提供的行属性区分分组行与详情行,再配合自定义样式和单元格渲染逻辑达成需求:
1. 区分分组行与详情行
TanStack Table的row对象提供getIsGroupRow()方法(或直接判断row.type === 'group'),可以精准识别当前行是否为分组行。
2. 添加差异化样式
根据判断结果给<tr>标签绑定不同的样式类或内联样式,比如给分组行设置灰色背景、加粗字体,和白色背景的详情行形成区分。
3. 优化分组行内容展示
可以通过单元格合并(colSpan)、自定义单元格渲染逻辑,让分组行的信息展示更直观,比如直接显示"年份:2022 总金额:xxx"。
修改后的完整代码
import { useState } from "react"; import { ColumnDef, useReactTable, getCoreRowModel, flexRender, getGroupedRowModel, getExpandedRowModel, } from "@tanstack/react-table"; type Asset = { name: string; year: number; amount: number; }; // 自定义样式,可根据需求调整 const tableStyles = { table: { width: '100%', borderCollapse: 'collapse' as const, }, cellBase: { border: '1px solid #ddd', padding: '8px', textAlign: 'left' as const, }, groupRow: { backgroundColor: '#f5f5f5', fontWeight: 'bold', }, detailRow: { backgroundColor: '#fff', }, }; export function App() { const columns: ColumnDef<Asset>[] = [ { accessorKey: "name", header: "名称", }, { accessorKey: "year", header: "年份", }, { accessorKey: "amount", header: "金额", aggregationFn: "sum", // 自定义分组行的金额单元格渲染 cell: (info) => { if (info.row.getIsGroupRow()) { return `总计: ${info.getValue()}`; } return info.getValue(); }, }, ]; const [data, setData] = useState([ { name: "项目1", year: 2022, amount: 31 }, { name: "项目2", year: 2022, amount: 4312 }, { name: "项目3", year: 2022, amount: 1233 }, { name: "项目4", year: 2024, amount: 123 }, { name: "项目5", year: 2024, amount: 512 }, ]); const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), getGroupedRowModel: getGroupedRowModel(), getExpandedRowModel: getExpandedRowModel(), state: { grouping: ["year"], }, }); return ( <table style={tableStyles.table}> <thead> {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th key={header.id} colSpan={header.colSpan} style={tableStyles.cellBase}> {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} </th> ))} </tr> ))} </thead> <tbody> {table.getRowModel().rows.map((row) => { const isGroupRow = row.getIsGroupRow(); return ( <tr key={row.id} style={isGroupRow ? tableStyles.groupRow : tableStyles.detailRow} > {row.getVisibleCells().map((cell) => ( <td key={cell.id} style={tableStyles.cellBase} // 分组行的名称列合并2个单元格,优化展示 colSpan={isGroupRow && cell.column.id === 'name' ? 2 : 1} > {cell.getIsPlaceholder() ? null : flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ); })} </tbody> </table> ); }
关键改动说明
- 样式区分:通过
row.getIsGroupRow()判断分组行,给<tr>应用不同的背景色和字体权重 - 分组行内容优化:金额列自定义渲染,分组行显示"总计: xxx";名称列合并2个单元格,让分组行的展示更简洁
- 样式管理:示例用内联样式,实际项目中可以改用CSS类名更便于维护
内容的提问来源于stack exchange,提问作者zkdiff
相关产品推荐
相关产品推荐

