前言
工作中面对将数组还原为树的需求应该说是相当常见,例如功能菜单,部门组织等等,都有类似的功能,需要前端将后端传递过来的扁平数组还原成带有层级关系的组织树,那么具体有哪些实现方法呢,今天就和大家一起来探讨探讨
问题分析
通过 id
和 pid
来关联数据,应该是我们工作中最常用的方案之一,存储模型如下
id | pid | data |
1 | 0 | a |
2 | 1 | b |
3 | 1 | c |
该模型代表了如下的树状结构
{ id: 1, pid: 0, data: 'a', children: [ {id: 2, pid: 1, data: 'b'}, {id: 3, pid: 1, data: 'c'}, ] } 复制代码
今天就以这种数据结构为例,来编写我们数组转树的方法
数组转树
const list = [ { pid: null, id: 1, data: "1" }, { pid: 1, id: 2, data: "2-1" }, { pid: 1, id: 3, data: "2-2" }, { pid: 2, id: 4, data: "3-1" }, { pid: 3, id: 5, data: "3-2" }, { pid: 4, id: 6, data: "4-1" }, ] 复制代码
递归
讲递归前顺便过一下数组中的 reduce
方法,对处理类似问题的结果时非常好用,可以让我们逐项处理数据时得到一个叠加的结果
reduce() 方法接收一个函数作为累加器,reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素
callback: 函数中主要用到的2个参数
- previousValue (上一次计算的结果或初始值)
- currentValue (当前的项)
例如我们简单的把数组的每一项都放在一个 tree
里边
const tree = list.reduce((res, item)=> { res.push(item) return res }, []) console.log(tree); 复制代码
本题使用递归时,需要明确我们递归过程得到返回值是一个数组,而最终结果是一个层级嵌套的数组,终止条件是循环遍历完每一项
function listToTree(list, pid) { const node = list.reduce((res, item) => { if (item.pid === pid) { // 匹配到该项进入下一层递归 const children = listToTree(list, item.id) item.children = children // 如果匹配到,返回上次结果 + 匹配项 return [...res, item] } // 如果没匹配到,返回上次结果 return res }, []) return node } console.log(listToTree(list, null)) 复制代码
迭代
运用迭代同样需要做循环遍历,只是这里我们会利用 js 对象是一个引用类型的特点,通过直接修改对象的属性,来最终确认层级关系
function listToTree(list, pid) { const map = {} const tree = [] list.forEach(item => { map[item.id] = item item.children = [] }) list.forEach(item => { if (item.pid === pid) { // 只添加根元素,其余通过映射关系关联 tree.push(item) } else { // 通过映射关系维护层级关系 map[item.pid].children.push(item) } }) return tree } console.log(listToTree(list, null)) 复制代码
这里使用了用 map 空间换时间,用于将所有项的 id 及自身记录到字典中,对执行效率进行了优化