递归处理后端传来的数据,处理成能使用的vue路由格式

思否 问答 👍 2 含 AI 优化版

问题

递归处理后端传来的数据,处理成能使用的vue路由格式

在 SegmentFault 查看原问题 →

由大模型辅助整理,保留原意,优化结构与可读性。

问题概述

后端返回的是一个“扁平数组”,每一项通过 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,方案二用普通对象),并预先放入一个 id0 的虚拟根节点。
  • 遍历数组时,把当前节点存入映射表;同时通过 pid 找到它的父节点(在映射表里一定已经存在,只要保证遍历顺序或提前按 pid 排序),把当前节点 push 到父节点的 children 数组里。
  • 因为 JavaScript 对象/数组是引用类型,“挂到父节点的 children 里”实际上是维护了同一份对象的多处引用,所以整个过程不需要写显式的递归函数,遍历一次数组即可完成树的构建。
  • 方案一额外用 JSON.parse(JSON.stringify(arr)) 做了一次深拷贝并按 pid 排序,是为了避免修改原始数据、并确保父节点在子节点之前被处理到;方案二则更简洁,直接假设遍历顺序不会有问题(如果数据本身不保证顺序,需要额外排序)。

小结

把扁平的 pid/id 数组转换成树形结构,经典做法是“建立 id 到节点的映射表 + 单次遍历挂载 children”,利用引用类型的特性省去了显式递归,是处理菜单、路由树、组织架构树等场景的通用技巧。

由大模型辅助整理,保留原意,补充结构与代码格式。

回答转载自 SegmentFault

评论

评论功能即将开放。你也可以通过 邮件GitHub Issue 反馈。