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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 11:45:37