- 前端
- UI组件
【免费下载链接】rsuite
🧱 A suite of React components .
树形表格是展示具有层级结构关系数据的表格形态,它通过在普通表格中引入“父子节点”与“展开/收起”交互,让数据之间的归属关系一目了然。本指南以 rsuite 组件库中的Table为核心,完整讲解树形表格的启用方式、层级数据组织、展开状态控制、自定义单元格与甘特图等实战写法,并结合仓库源码说明其底层行为,读完即可在业务项目中落地一套可运行的层级数据表格。
树形表格是什么
树形表格是一种在表格中展示有结构关系(父子层级)数据的呈现方式。与普通平铺表格相比,树形表格能够清晰地展示数据之间的层级关系——例如部门与成员、分类与商品、任务与子任务。rsuite 的Table组件原生支持树形模式,只需一个属性即可开启,不需要额外引入第三方树表库。
从源码结构看,rsuite 的Table组件是对底层rsuite-table的再封装,见 src/Table/Table.tsx,它对外暴露Table.Cell、Table.Column、Table.HeaderCell、Table.ColumnGroup四个子组件,树形相关的渲染与展开逻辑由底层表格引擎承担,rsuite 负责将isTree、expandedRowKeys、rowKey、renderTreeToggle、onExpandChange等属性透传下去。
基本使用:isTree + children 层级数据
启用树形表格只需要两步:
- 在
Table组件上设置isTree属性; - 让
data中的父子关系通过children字段来定义。
典型的数据结构如下:
const data = [ { id: '1', name: 'Car', count: 460, children: [ { id: '1-1', name: 'Mercedes Benz', count: 300 } // ...更多子节点 ] } ]; <Table data={data} isTree rowKey="id">其中rowKey="id"用于指定行数据的唯一标识字段。树形表格在渲染时会根据rowKey追踪每一行的身份,从而正确记录“哪个节点被展开/收起”;如果没有唯一标识,树形节点的展开状态将无法被可靠地维护。
层级结构可以无限嵌套,即children内部的节点还可以继续拥有自己的children,从而形成多级树。这一点在官方演示“简单甘特图”中体现得很充分(见下文)。
树形表格相关属性速查
与树形表格直接相关的属性如下:
| 属性 | 类型 | 描述 |
|---|---|---|
expandedRowKeys | string[] | 展开的行的key数组 |
defaultExpandedRowKeys | string[] | 默认展开的行的key数组 |
rowKey | string | 行数据的唯一标识 |
renderTreeToggle | () => ReactNode | 自定义展开/关闭节点的图标 |
onExpandChange | (expanded: boolean, rowData: RowData) => void | 展开/关闭节点的回调函数 |
使用要点:
- 受控与默认值:
expandedRowKeys是受控属性,传入后由你维护展开集合;如果希望组件自己管理展开状态,并设置“首次渲染时默认展开哪些行”,则使用defaultExpandedRowKeys。除此之外,演示中还出现了defaultExpandAllRows,用于一次性展开全部层级。 - 回调时机:
onExpandChange在节点展开/收起时触发,回调参数为(expanded, rowData),第一个参数是布尔值(展开为true,收起为false),第二个参数是当前行数据。 - 自定义图标:
renderTreeToggle允许你完全替换默认的展开/收起箭头图标。
展开/收起交互的底层依据
展开状态的记录依赖rowKey。从 src/Table/Table.tsx 中可以看到,rsuite 在封装时把rowData与dataKey作为Cell的两个关键属性保留:dataKey既是数据绑定键,也是排序键。树形表格在展开节点时会基于rowKey生成展开集合,并把“当前行是否处于展开态”作为状态附着在行上;因此自定义单元格(Cell)必须能拿到rowData,才能正确渲染每个节点自己的展开/收起图标与缩进层级。
注意:树形表格的自定义单元格,需要将
rowData传递给渲染树的Cell,因为在Cell内部将使用它来记录节点的状态(参见 rsuite issue #2666)。
正确写法:
const CustomCell = ({ rowData, ...rest }) => { return ( <Cell rowData={rowData} {...rest}> {rowData.name} </Cell> ); };如果不把rowData传回Cell,则单元格内部无法感知所在行是否为父节点、是否已展开,树形图标与层级缩进就会出现异常。
完整演示一:基础树形表格
先看一个完整的、可直接运行的树形表格示例(对应官方演示tree.md)。它使用mockTreeData生成三层级数据,第一层为汽车厂商、第二层为燃料类型、第三层为具体车型,并渲染“评分(Rating)”与“价格(Price)”两列:
import { Table } from 'rsuite'; import { faker } from '@faker-js/faker'; import { mockTreeData } from './mock'; const { Column, HeaderCell, Cell } = Table; const data = mockTreeData({ limits: [2, 3, 3], // 每一层的节点数量:第一层 2 个,第二层 3 个,第三层 3 个 labels: layer => { if (layer === 0) { return faker.vehicle.manufacturer(); // 第一层:汽车厂商 } else if (layer === 1) { return faker.vehicle.fuel(); // 第二层:燃料类型 } return faker.vehicle.vehicle(); // 第三层:具体车型 }, getRowData: () => ({ id: faker.string.numeric(5), price: faker.commerce.price({ min: 10000, max: 1000000, dec: 0, symbol: '$' }), rating: faker.finance.amount({ min: 2, max: 5 }) }) }); const App = () => { return ( <Table isTree defaultExpandAllRows bordered cellBordered rowKey="id" height={400} data={data} /** shouldUpdateScroll: 数据更新后是否更新滚动条,关闭后滚动位置保持不变 **/ shouldUpdateScroll={false} onExpandChange={(isOpen, rowData) => { console.log(isOpen, rowData); }} > <Column flexGrow={1}> <HeaderCell>Vehicle 🚗</HeaderCell> <Cell dataKey="label" /> </Column> <Column width={180}> <HeaderCell>Rating ⭐️</HeaderCell> <Cell> {rowData => Array.from({ length: rowData.rating }).map((_, i) => <span key={i}>⭐️</span>) } </Cell> </Column> <Column width={100}> <HeaderCell>Price 💰</HeaderCell> <Cell dataKey="price" /> </Column> </Table> ); }; ReactDOM.render(<App />, document.getElementById('root'));示例中的几个关键点:
mockTreeData是 rsuite 文档工程提供的树形数据生成工具,源码见 docs/utils/mock.ts。它根据limits数组确定每层节点数,通过labels函数按层生成标签,用getRowData注入业务字段,并在非末层节点上自动挂上children数组。- 第三列的
Cell以函数形式渲染,接收rowData作为参数,依据rating值循环输出星星——这正体现了树形表格单元格可以自由使用行数据。 shouldUpdateScroll={false}表示数据更新后不重置滚动条位置,避免每次展开/收起节点时视图跳动。
完整演示二:用树形表格实现简单甘特图
树形表格的典型进阶用法是“简单甘特图”:把项目生命周期按“任务 → 子任务 → 孙任务”组织成树,每一列代表一个日期,单元格按日期范围着色,从而形成可视化进度条效果(对应官方演示gantt.md)。
数据与列定义如下:
import { Table } from 'rsuite'; import { faker } from '@faker-js/faker'; const { Column, ColumnGroup, HeaderCell, Cell } = Table; const data = [ { id: '1', task: 'Project Lifecycle', startDate: '2024-01-01', endDate: '2024-01-31', children: [ { id: '1-1', task: 'Requirements analysis', startDate: '2024-01-01', endDate: '2024-01-05', children: [ { id: '1-1-1', task: 'Gathering requirements', startDate: '2024-01-01', endDate: '2024-01-02' }, { id: '1-1-2', task: 'Documenting requirements', startDate: '2024-01-03', endDate: '2024-01-05' } ] }, // ...Design / Development / Testing / Deployment / Maintenance 等子任务 ] } ]; const columns = Array.from({ length: 31 }).map((_, index) => { return { HeaderCell: props => { return <HeaderCell {...props}>{index + 1}</HeaderCell>; }, Cell: ({ rowData, depth, ...rest }) => { const colors = ['#c8f0c7', '#4cb04f', '#0f9119']; // 不同层级的着色 const startDate = new Date(rowData.startDate); const endDate = new Date(rowData.endDate); const day = index + 1; const inRange = startDate.getDate() <= day && day <= endDate.getDate(); return ( <Cell {...rest} depth={depth} style={{ backgroundColor: inRange ? colors[depth] : 'transparent' }} /> ); } }; }); const App = () => { return ( <Table isTree defaultExpandAllRows bordered cellBordered rowKey="id" autoHeight data={data} rowHeight={30} > <Column width={300} fixed> <HeaderCell>Project Task</HeaderCell> <Cell dataKey="task" style={{ padding: '5px 0' }} /> </Column> {columns.map((column, index) => { return ( <Column key={index} width={40} align="center"> <column.HeaderCell /> <column.Cell /> </Column> ); })} </Table> ); }; ReactDOM.render(<App />, document.getElementById('root'));这个例子的关键技巧:
depth参数:自定义Cell函数签名中的depth表示当前行所处的树形层级(0 表示顶层)。示例用它决定着色颜色colors[depth],层级越深颜色越深,视觉上直接区分任务粒度。- 固定任务列:任务列设置
width={300} fixed,横向滚动时任务名称列始终可见,日期列在右侧滚动。 - 按日着色:每个日期列判断当前行
startDate/endDate是否覆盖该日,覆盖则填充背景色,形成条形进度效果。 autoHeight:让表格高度随内容自适应,配合多层级数据展示完整的甘特图。ColumnGroup:本示例中未实际使用分组表头,但它与Column、HeaderCell、Cell一同由Table导出(见 src/Table/Table.tsx),适合在需要“任务名 + 31 个日期日”这种分组表头结构时使用。
指定树形列:treeCol 属性
默认情况下,表格的第一列会被用作树形列(即展开图标与缩进所在列)。你也可以通过<Table.Column>上的treeCol属性,把任意一列指定为树形列(对应官方演示tree-col.md)。
<Table isTree defaultExpandAllRows bordered cellBordered rowKey="id" height={400} data={data} shouldUpdateScroll={false} onExpandChange={(isOpen, rowData) => { console.log(isOpen, rowData); }} > <Column width={100}> <HeaderCell>Code</HeaderCell> <Cell dataKey="id" /> </Column> <Column flexGrow={1} treeCol> <HeaderCell>Vehicle 🚗</HeaderCell> <Cell dataKey="label" /> </Column> {/* Rating / Price 列略 */} </Table>这里把treeCol加在了“Vehicle”列上,因此展开/收起图标与层级缩进都出现在该列,而“Code”列保持为普通数据列。这在业务中很实用——例如第一列是“编号”,第二列才是“名称”,此时让名称列承载树形交互更符合阅读习惯。
虚拟化树形表格:大数据的性能方案
树形表格支持虚拟滚动,可以大大提高渲染大量数据的性能(对应官方演示virtualized.md)。启用方式是在Table上同时设置isTree与virtualized:
<Table isTree virtualized defaultExpandAllRows bordered cellBordered rowKey="id" height={400} data={data} shouldUpdateScroll={false} onExpandChange={(isOpen, rowData) => { console.log(isOpen, rowData); }} > <Column flexGrow={1}> <HeaderCell>Vehicle 🚗</HeaderCell> <Cell dataKey="label" /> </Column> <Column width={180}> <HeaderCell>Rating ⭐️</HeaderCell> <Cell> {rowData => Array.from({ length: rowData.rating }).map((_, i) => <span key={i}>⭐️</span>) } </HeaderCell> </Column> <Column width={100}> <HeaderCell>Price 💰</HeaderCell> <Cell dataKey="price" /> </Column> </Table>虚拟化示例中使用了limits: [10, 20, 40]的数据规模(首层 10、次层 20、末层 40,共 10 + 200 + 800 = 1010 行),直观验证了在大量层级节点下表格仍能流畅滚动。其原理是:虚拟滚动只渲染视口范围内可见的行,行数据量再大,DOM 节点数量也保持恒定,从而避免一次性渲染全部行导致的内存与渲染开销。
使用虚拟化时的注意点:
- 建议与
rowKey配合使用,让虚拟滚动器能够稳定识别每一行; - 树形表格的展开状态仍需通过
rowKey维护,展开大量节点后视口外的行同样不会被创建,滚动到对应位置时才渲染; height用于约束可视区域高度,是虚拟滚动生效的前提之一。
在文档工程中查看与复现这些演示
以上四个演示(树形展示、简单甘特图、指定树形列、虚拟化树形表格)均来自本仓库文档工程的 fragments 目录,对应的 Markdown 片段文件分别为:
- tree.md
- gantt.md
- tree-col.md
- virtualized.md
这些片段由页面入口 index.tsx 注入示例容器。该入口通过 docs/utils/mock.ts 中的mockTreeData生成示例数据,并把mock.js作为沙箱文件一并提供给在线运行环境(CodeSandbox),因此你既可以在文档页面上直接交互体验,也可以把示例代码复制到自己的 React + rsuite 项目中运行。
小结
- 树形表格用于展示有层级结构的数据,开启方式是在
Table上设置isTree,并用children组织父子关系,用rowKey标注行唯一标识。 - 展开状态由
expandedRowKeys(受控)/defaultExpandedRowKeys(初始默认值)/defaultExpandAllRows(全部展开)控制,通过onExpandChange感知展开事件,通过renderTreeToggle自定义图标。 - 自定义单元格必须把
rowData回传给Cell,否则树形状态无法正常记录与渲染。 - 默认使用第一列作为树形列,可用
Column上的treeCol属性改为指定列。 - 数据量大时可同时开启
isTree与virtualized获得虚拟滚动能力,示例表明在约千行的三层级数据下依然流畅。
- 前端
- UI组件
【免费下载链接】rsuite
🧱 A suite of React components .
相关推荐
grpc-cj跨平台编译与部署指南:Windows/Linux/macOS三端实战(附CANGJIE_STDX避坑清单)
grpc cj跨平台编译与部署指南:Windows/Linux/macOS三端实战(附CANGJIE_STDX避坑清单) 本文是 grpc cj 跨平台编译与部
前端UI组件仓颉开发者必看:TPC-Cangjie-HarmonyOS-API 是什么?一文讲透仓颉调用 HarmonyOS API 的完整指南
仓颉开发者必看:TPC Cangjie HarmonyOS API 是什么?一文讲透仓颉调用 HarmonyOS API 的完整指南 🚀 TPC Cangji
前端UI组件vxe-table虚拟树表格实战:层级数据展示优化
vxe table虚拟树表格实战:层级数据展示优化 一、业务痛点与技术挑战 在企业级应用开发中,层级数据(如文件目录、组织架构、分类目录)的高效展示一直是前端开
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考