递归处理后端传来的数据,处理成能使用的vue路由格式
思否 问答 👍 2 含 AI 优化版
问题
递归处理后端传来的数据,处理成能使用的vue路由格式
由大模型辅助整理,保留原意,优化结构与可读性。
问题概述
后端返回的是一个“扁平数组”,每一项通过 pid(父级 id)和 id 表达父子关系,提问者希望把这种扁平结构递归处理成带有 children 嵌套的树形结构,以便直接用作 Vue 路由(或菜单树)的数据格式。
涉及技术
JavaScript、递归/树形结构转换、Map、Vue 路由
期望结果
得到一段可靠的转换代码,能把 [{pid, id, name}, ...] 这种扁平数组转换成带 children 的树形结构。
回答
原始回答
2018-09-07
突然想起之前搞的一个东西
var rr =
[
{"pid":0,"id":3,"name":"最外层3"},
{"pid":0,"id":4,"name":"最外层4"},
{"pid":4,"id":5,"name":"最外层-4"},
{"pid":5,"id":6,"name":"最外层-4-1"},
{"pid":0,"id":7,"name":"最外层7"},
{"pid":7,"id":8,"name":"最外层-7"},
{"pid":0,"id":9,"name":"最外层9"},
{"pid":9,"id":10,"name":"最外层9-1"},
{"pid":9,"id":11,"name":"最外层9-2"},
{"pid":11,"id":12,"name":"最外层9-2-1"}
];
((arr)=>{
arr = JSON.parse(JSON.stringify(arr)).sort((n,m)=>n.pid-m.pid);
var _objHash = new Map();
_objHash.set(0,{});
arr.forEach(v=>{
_objHash.set(v.id,v);
var _temp = _objHash.get(v.pid)
if(_temp.children){
_temp.children.push(v)
}else{
_temp.children = [v]
}
});
return _objHash.get(0)
})(rr);
2018年9月7日10:01:30 正好有个朋友问我,重新写了一个。想法就是通过引用,直接链起来
r = [
{"pid":0,"id":3,"name":"最外层3"},
{"pid":0,"id":4,"name":"最外层4"},
{"pid":4,"id":5,"name":"最外层-4"},
{"pid":5,"id":6,"name":"最外层-4-1"},
{"pid":0,"id":7,"name":"最外层7"},
{"pid":7,"id":8,"name":"最外层-7"},
{"pid":0,"id":9,"name":"最外层9"},
{"pid":9,"id":10,"name":"最外层9-1"},
{"pid":9,"id":11,"name":"最外层9-2"},
{"pid":11,"id":12,"name":"最外层9-2-1"}
]
var _obj = {0:{}};
r.forEach(v=>{
_obj[v.id] = v;
if(!_obj[v.pid].children) _obj[v.pid].children = []
_obj[v.pid].children.push(v)
})
console.log(_obj,_obj[0])
AI 优化 · 回答整理
解答思路
核心思路是用一个哈希表(Map 或普通对象)存下每个节点的引用,遍历一次数组,把每个节点按 pid 挂到对应父节点的 children 数组下,这样只需要遍历一次就能利用对象引用把树连起来,不需要写真正的递归函数。
示例代码
// 方案一:使用 Map
var rr = [
{ pid: 0, id: 3, name: "最外层3" },
{ pid: 0, id: 4, name: "最外层4" },
{ pid: 4, id: 5, name: "最外层-4" },
{ pid: 5, id: 6, name: "最外层-4-1" },
{ pid: 0, id: 7, name: "最外层7" },
{ pid: 7, id: 8, name: "最外层-7" },
{ pid: 0, id: 9, name: "最外层9" },
{ pid: 9, id: 10, name: "最外层9-1" },
{ pid: 9, id: 11, name: "最外层9-2" },
{ pid: 11, id: 12, name: "最外层9-2-1" }
];
((arr) => {
arr = JSON.parse(JSON.stringify(arr)).sort((n, m) => n.pid - m.pid);
var _objHash = new Map();
_objHash.set(0, {});
arr.forEach((v) => {
_objHash.set(v.id, v);
var _temp = _objHash.get(v.pid);
if (_temp.children) {
_temp.children.push(v);
} else {
_temp.children = [v];
}
});
return _objHash.get(0);
})(rr);
// 方案二:使用普通对象,写法更简洁
var r = [
{ pid: 0, id: 3, name: "最外层3" },
{ pid: 0, id: 4, name: "最外层4" },
{ pid: 4, id: 5, name: "最外层-4" },
{ pid: 5, id: 6, name: "最外层-4-1" },
{ pid: 0, id: 7, name: "最外层7" },
{ pid: 7, id: 8, name: "最外层-7" },
{ pid: 0, id: 9, name: "最外层9" },
{ pid: 9, id: 10, name: "最外层9-1" },
{ pid: 9, id: 11, name: "最外层9-2" },
{ pid: 11, id: 12, name: "最外层9-2-1" }
];
var _obj = { 0: {} };
r.forEach((v) => {
_obj[v.id] = v;
if (!_obj[v.pid].children) _obj[v.pid].children = [];
_obj[v.pid].children.push(v);
});
console.log(_obj, _obj[0]);
详细说明
- 两种方案本质相同:先建立一个 id 到节点对象的映射表(方案一用
Map,方案二用普通对象),并预先放入一个id为0的虚拟根节点。 - 遍历数组时,把当前节点存入映射表;同时通过
pid找到它的父节点(在映射表里一定已经存在,只要保证遍历顺序或提前按pid排序),把当前节点push到父节点的children数组里。 - 因为 JavaScript 对象/数组是引用类型,“挂到父节点的
children里”实际上是维护了同一份对象的多处引用,所以整个过程不需要写显式的递归函数,遍历一次数组即可完成树的构建。 - 方案一额外用
JSON.parse(JSON.stringify(arr))做了一次深拷贝并按pid排序,是为了避免修改原始数据、并确保父节点在子节点之前被处理到;方案二则更简洁,直接假设遍历顺序不会有问题(如果数据本身不保证顺序,需要额外排序)。
小结
把扁平的 pid/id 数组转换成树形结构,经典做法是“建立 id 到节点的映射表 + 单次遍历挂载 children”,利用引用类型的特性省去了显式递归,是处理菜单、路由树、组织架构树等场景的通用技巧。
由大模型辅助整理,保留原意,补充结构与代码格式。
回答转载自 SegmentFault