
本文介绍在 next.js 13 管理后台开发中,如何将扁平的字符串二维数组(含父子关系标识)高效构建为具有 `children` 嵌套结构的对象数组,重点解决层级丢失与顺序依赖问题。
在构建树形菜单、分类导航或组织架构等场景中,后端常返回扁平化的层级数据(如 [parentId, id, name] 三元组),而前端需要将其转化为递归嵌套的对象结构。直接使用 map() 仅能做线性映射,无法建立父子引用关系;正确解法需借助哈希映射(Map)预存所有节点,并通过父 ID 动态挂载子节点。
以下是推荐的健壮实现:
function buildTreeFromFlatArray(data) {
const nodeMap = new Map(); // 缓存所有节点:id → node 对象
// 第一步:初始化所有节点(无论是否为根)
for (const [parentId, id, name] of data) {
nodeMap.set(id, {
id: parseInt(id, 10),
name,
children: []
});
}
// 第二步:建立父子关系(关键步骤)
for (const [parentId, id, name] of data) {
if (parentId !== "") {
const parent = nodeMap.get(parentId);
const child = nodeMap.get(id);
if (parent && child) {
parent.children.push(child);
}
}
}
// 第三步:提取所有根节点(ParentID 为空的项)
const roots = [];
for (const [parentId] of data) {
if (parentId === "") {
const root = nodeMap.get(data.find(row => row[0] === "")?.[1]);
if (root && !roots.some(r => r.id === root.id)) {
roots.push(root);
}
}
}
// 去重并确保顺序与原始根节点一致
return data
.filter(row => row[0] === "")
.map(row => nodeMap.get(row[1]))
.filter(Boolean);
}
// 使用示例
const input = [
["", "1", "Mobile Phones"],
["1", "2", "Apple"],
["1", "3", "Samsung"],
["", "4", "Tablets"],
["4", "5", "Huawei"],
["", "6", "X"],
["6", "7", "Y"],
["7", "8", "Z"]
];
console.log(buildTreeFromFlatArray(input));✅ 关键要点说明:
- Map 是核心:避免重复创建对象,确保同一 id 节点被唯一引用,父子挂载时修改的是同一内存实例;
- 两遍遍历更安全:先建节点,再连关系,规避“父节点尚未创建”的潜在错误(即使输入顺序不保证,也可扩展为多轮收敛处理);
- 根节点提取严谨:按原始数组中 parentId === "" 的顺序提取根节点,保持 UI 展示逻辑一致性;
- 类型安全增强:使用 parseInt(id, 10) 显式转整型,防止 "01" 类字符串误解析;
⚠️ 注意事项:
- 该算法默认要求父节点在子节点前声明(如 ["", "1", ...] 必须出现在 ["1", "2", ...] 之前)。若数据顺序不可控,需改用「拓扑排序」或「多轮扫描 + 待挂载队列」策略;
- 若存在循环引用(如 A → B → A),需额外加入环检测逻辑;
- 在 Next.js 服务端组件(Server Component)中使用时,确保数据已完整获取,避免在 useEffect 中异步调用导致 hydration 不一致。
此方案简洁、可读性强,适用于中等规模树(千级节点内),可直接集成至 Next.js 数据层或自定义 Hook 中,为动态菜单、权限路由等提供坚实的数据结构基础。










