核心是用 reduce 构建 id 映射和 roots 列表:先存所有节点到 map 并初始化 children,若 parent_id 为根值则入 roots,否则挂到父节点 children 下;最终返回 roots 即树形结构。

用 reduce() 把扁平数组转成带父子结构的树形 JSON,核心在于:**按 parent_id 关联子节点,并找到顶层(parent_id 为空或为指定根值)节点作为入口**。不需要递归调用 reduce,一次遍历 + 哈希索引就能完成。
明确数据结构和根标识
扁平数组通常类似:
[ { id: 1, name: "首页", parent_id: null }, { id: 2, name: "产品", parent_id: null }, { id: 3, name: "Web端", parent_id: 2 }, { id: 4, name: "App端", parent_id: 2 }, { id: 5, name: "登录页", parent_id: 3 } ]关键点:
- 每个项有唯一 id
- 用 parent_id 指向父节点(可能是
null、undefined或字符串"0",需统一判断) - 顶层节点的
parent_id应视为“无父”,比如null、0或""
用 reduce 构建 id → 节点映射 + 子节点数组
分两步走:先建立所有节点的引用映射,再把每个节点挂到其父节点的 children 下。reduce 在这里用于累积一个包含两个部分的对象:map(id 到节点)和 roots(顶层节点列表)。
示例代码:
const listToTree = (list, idKey = 'id', parentKey = 'parent_id', rootValue = null) => { return list.reduce((acc, item) => { const id = item[idKey]; const parentId = item[parentKey]; // 初始化当前节点(确保 children 数组存在) const node = { ...item, children: [] }; // 存入 map,方便后续查找父节点 acc.map[id] = node; // 如果是顶层节点,推入 roots if (parentId === rootValue || parentId == null) { acc.roots.push(node); } else { // 找到父节点,把当前节点加进它的 children const parent = acc.map[parentId]; if (parent) { parent.children.push(node); } // 若父节点还没出现(顺序不保证),暂不处理;后续总会遇到 } return acc; }, { map: {}, roots: [] }).roots; };处理“父节点后出现”的情况(健壮性增强)
上面代码假设父节点可能在子节点之后才出现。为避免 parent.children.push 失败,可分两轮:第一轮只建 map 和 roots;第二轮再补 children。但 reduce 本身适合单次遍历,更简洁做法是——在第一次遍历时,对未就位的父节点延迟挂载:
- 遇到子节点时,若父节点不在 map 中,先记录一条“待挂载关系”(如
{ parentId, childNode }) - 最后再遍历待挂载列表,补上 children
不过实践中,只要最终执行一次 roots 返回,且后续不依赖中间状态,多数场景下直接用上面单次 reduce 就足够稳定 —— 因为 JS 对象属性访问快,即使父节点后到,只要在同次 reduce 中后续遇到它,acc.map[parentId] 就会被创建,子节点也能正确挂上。
使用与输出示例
调用:
const tree = listToTree(list);结果(简化):
[ { "id": 1, "name": "首页", "parent_id": null, "children": [] }, { "id": 2, "name": "产品", "parent_id": null, "children": [ { "id": 3, "name": "Web端", "parent_id": 2, "children": [ { "id": 5, "name": "登录页", "parent_id": 3, "children": [] } ] }, { "id": 4, "name": "App端", "parent_id": 2, "children": [] } ] } ]这就是标准的递归 JSON 树结构,可直接用于 Tree 组件、菜单渲染或后端接口返回。











