开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第23天,点击查看活动详情
前言
工作中面对将数组还原为树的需求应该说是相当常见,例如功能菜单,部门组织等等,都有类似的功能,需要前端将后端传递过来的扁平数组还原成带有层级关系的组织树,那么具体有哪些实现方法呢,今天就和大家一起来探讨探讨
问题分析
通过 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 及自身记录到字典中,对执行效率进行了优化