引言
在现代Web应用中,树形数据结构的展示需求日益增多,例如项目管理、分类目录、组织架构等场景。Handsontable作为一款强大的JavaScript数据表格库,支持通过插件或自定义逻辑实现树形数据。本文将深入探讨两种实现方案:Nested Rows插件(官方推荐)和手动实现(自定义渲染器+折叠逻辑),并提供完整代码和避坑指南。
方案1:使用Nested Rows插件(官方推荐)
核心优势
- 官方维护,稳定性高
- 自动处理折叠、展开、父子关系
- 无需手动维护层级逻辑
## 1.1 环境配置
确保已安装Handsontable及Nested Rows插件:
npm install handsontable @handsontable/react # React项目
# 或直接引入CDN
## 1.2 数据结构定义
通过__children
字段声明子节点:
const data = [
{
name: '项目合计',
__children: [
{
name: '战略项目',
__children: [
{ name: '战略项目1' },
{ name: '战略项目2' }
]
},
{
name: '商业项目',
__children: [
{ name: '商业项目1' },
{ name: '商业项目2' }
]
}
]
}
];
## 1.3 表格初始化
配置nestedRows
插件和自定义渲染器:
import Handsontable from 'handsontable';
import { registerPlugin, NestedRows } from 'handsontable/plugins/nestedRows';
registerPlugin(NestedRows); // 注册插件
const container = document.getElementById('hot');
const hot = new Handsontable(container, {
data: data,
plugins: ['nestedRows'],
nestedRows: true, // 启用树形模式
colHeaders: ['项目名称'],
columns: [
{
data: 'name',
renderer: function(instance, td, row, col, prop, value) {
// 动态计算缩进
const level = instance.getPlugin('nestedRows').getRowLevel(row);
td.style.paddingLeft = `${level * 20}px`;
td.textContent = value;
}
}
],
// 可选:配置折叠图标
renderAllRows: false,
manualRowMove: true
});
## 1.4 功能扩展
通过插件API实现更多交互:
// 获取所有父节点
const parentRows = hot.getPlugin('nestedRows').getParentRows();
// 折叠所有节点
hot.getPlugin('nestedRows').collapseAll();
方案2:手动实现(自定义逻辑)
适用场景
- 避免插件依赖
- 需要深度定制折叠逻辑
## 2.1 数据结构扁平化
使用level
和parent
字段标识层级:
const data = [
{ id: 7, name: '项目合计', level: 0, parent: null, collapsed: false },
{ id: 8, name: '战略项目', level: 1, parent: 7, collapsed: false },
{ id: 9, name: '战略项目1', level: 2, parent: 8, collapsed: true },
{ id: 10, name: '战略项目2', level: 2, parent: 8, collapsed: true },
{ id: 11, name: '商业项目', level: 1, parent: 7, collapsed: false },
{ id: 12, name: '商业项目1', level: 2, parent: 11, collapsed: true },
{ id: 13, name: '商业项目2', level: 2, parent: 11, collapsed: true }
];
## 2.2 自定义渲染器
动态渲染缩进和折叠图标:
const hot = new Handsontable(container, {
data: data,
colHeaders: ['项目名称'],
columns: [
{
data: 'name',
renderer: function(instance, td, row, col, prop, value, cellProperties) {
const rowData = instance.getDataAtRow(row);
td.style.paddingLeft = `${rowData.level * 20}px`; // 缩进
// 添加折叠图标
if (rowData.level < 2) {
const icon = rowData.collapsed ? '▶' : '▼';
td.innerHTML = `${icon} ${value}`;
} else {
td.textContent = value;
}
}
}
]
});
## 2.3 折叠逻辑实现
绑定点击事件控制子节点显隐:
hot.addHook('afterOnCellMouseDown', (event, coords) => {
const row = coords.row;
const rowData = hot.getDataAtRow(row);
if (rowData.level >= 2) return;
rowData.collapsed = !rowData.collapsed;
const children = data.filter(item => item.parent === rowData.id);
children.forEach(child => {
child.hidden = rowData.collapsed; // 假设有hidden字段控制显隐
});
hot.render();
});
## 2.4 性能优化
- 使用
batchUpdate
减少渲染次数 - 添加防抖逻辑避免频繁操作
完整代码实例
Nested Rrows插件方案(React示例)
import React from 'react';
import { HotTable } from '@handsontable/react';
import { registerPlugin, NestedRows } from 'handsontable/plugins/nestedRows';
registerPlugin(NestedRows);
const data = [ /* 同上文数据结构 */ ];
export default function TreeTable() {
return (
<HotTable
data={data}
plugins={['nestedRows']}
nestedRows={true}
colHeaders={['项目名称']}
columns={[{ data: 'name' }]}
height="600"
licenseKey="non-commercial-and-evaluation"
/>
);
}
手动实现方案(完整JavaScript)
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.css" rel="stylesheet">
</head>
<body>
<div id="hot"></div>
<script>
// 完整代码见上文方案2
</script>
</body>
</html>
方案对比与选型建议
维度 | Nested Rows插件 | 手动实现 |
---|---|---|
开发效率 | 高(自动处理层级逻辑) | 低(需编写大量自定义代码) |
维护成本 | 低(官方维护) | 高(需自行处理边界条件) |
灵活性 | 中(依赖插件功能) | 高(完全可控) |
性能 | 优(内置优化) | 中(依赖实现细节) |
选型建议:
- 快速交付项目 ➜ 使用Nested Rows插件
- 深度定制需求 ➜ 手动实现
常见问题解答
Q1:Nested Rows插件是否支持异步加载子节点?
支持,可通过afterCollapse
事件动态加载数据。
Q2:手动实现时如何避免渲染卡顿?
- 使用
requestAnimationFrame
优化渲染 - 减少DOM操作,尽量批量更新