
本文介绍如何将通过多次 ajax 请求获取的三层扁平员工数据(根节点、一级子节点、二级子节点)按 `employeeid` 和父子关系动态组装为符合 treegrid 要求的嵌套树形结构,并提供可复用的递归挂载与扁平化工具函数。
在实际开发中(如使用 jqxTreeGrid、Ant Design Tree 或自定义树组件),常需将分层拉取的扁平数据构建成具有 children 属性的嵌套树结构。典型场景是:
- 第一次请求获取顶级员工(如 EmployeeID: 2);
- 第二次请求获取其直属下属(EmployeeID: [1,3,4,5,8]);
- 第三次请求获取下级下属(如 EmployeeID: [6,7,9],隶属于 EmployeeID: 5)。
关键挑战在于:如何根据业务逻辑(如 ReportsTo 字段或预定义层级映射)将子数组精准挂载到对应父节点的 children 属性中? 原始数据中并未显式包含 ReportsTo 字段,因此需依赖外部约定(例如:第二层数组中的所有项均属于第一层中 EmployeeID: 2 的子节点;第三层数组中的项均属于第二层中 EmployeeID: 5 的子节点)。以下提供两种主流实现方式:
✅ 方案一:基于层级顺序 + 显式挂载(推荐用于可控数据源)
假设你已知层级归属关系(如后端返回时附带 parentId 字段),最健壮的方式是统一建模后递归挂载:
// 模拟三组异步获取的数据
const level0 = [{ EmployeeID: 2, FirstName: "Andrew", ... }]; // 根节点
const level1 = [
{ EmployeeID: 8, FirstName: "Laura", ReportsTo: 2 },
{ EmployeeID: 1, FirstName: "Nancy", ReportsTo: 2 },
{ EmployeeID: 5, FirstName: "Steven", ReportsTo: 2 }
];
const level2 = [
{ EmployeeID: 6, FirstName: "Michael", ReportsTo: 5 },
{ EmployeeID: 7, FirstName: "Robert", ReportsTo: 5 },
{ EmployeeID: 9, FirstName: "Anne", ReportsTo: 5 }
];
// 合并为单层扁平数组(含 parentId)
const allNodes = [...level0, ...level1, ...level2];
// 构建树:O(n) 时间复杂度
function buildTree(nodes, idKey = 'EmployeeID', parentKey = 'ReportsTo') {
const nodeMap = new Map();
const roots = [];
// 第一遍:建立 ID → 节点映射
nodes.forEach(node => {
nodeMap.set(node[idKey], { ...node, children: [] });
});
// 第二遍:挂载子节点
nodes.forEach(node => {
const parentId = node[parentKey];
const currentNode = nodeMap.get(node[idKey]);
if (parentId == null || !nodeMap.has(parentId)) {
roots.push(currentNode);
} else {
nodeMap.get(parentId).children.push(currentNode);
}
});
return roots;
}
const treeData = buildTree(allNodes);
console.log(treeData); // 输出符合要求的嵌套结构⚠️ 注意:若原始数据无 ReportsTo,需在请求时明确传递上下文(如 /api/employees?parent=2),或由前端维护映射表(如 parentMap = {2: [1,3,4,5,8], 5: [6,7,9]})。
✅ 方案二:按预设层级顺序手动组装(适用于固定结构)
若层级关系严格固定(如“第二组一定属于第一组首个元素”),可用简洁方式手动挂载:
// 假设 level0 仅含一个根节点
if (level0.length > 0) {
level0[0].children = level1;
// 手动查找 level1 中 EmployeeID === 5 的节点,并挂载 level2
const managerNode = level1.find(emp => emp.EmployeeID === 5);
if (managerNode) {
managerNode.children = level2;
managerNode.expanded = "true"; // 可选:控制默认展开
}
}
// level0 即为最终树根
const finalTree = level0;? 辅助函数:嵌套结构 ↔ 扁平数组互转
开发调试时,常需将嵌套树转为扁平列表(如用于搜索、导出):
// 嵌套 → 扁平(广度优先)
function nestedToLinear(data) {
const result = [];
const queue = [...data];
while (queue.length > 0) {
const node = queue.shift();
result.push({ ...node, children: undefined }); // 移除 children 避免循环引用
if (Array.isArray(node.children) && node.children.length > 0) {
queue.push(...node.children);
}
}
return result;
}
// 使用示例
console.log(nestedToLinear(treeData)); // 输出单层数组,含全部节点✅ 总结
- 核心原则:树结构构建 = “建立节点索引 + 按关系挂载”,而非硬编码 children: [...];
- 生产建议:后端应返回 id + parentId 字段,前端用 buildTree() 统一处理,避免耦合层级逻辑;
- 性能注意:Map 查找为 O(1),整套构建为 O(n),远优于嵌套循环;
- 扩展性:该模式天然支持 N 层嵌套,无需修改逻辑。
通过以上方法,你可灵活将任意数量的扁平数组组合为标准树形数据,无缝对接各类 TreeGrid 组件。










