将一个形如下面的对象数组转换成一个树形结构的数组
let dataArr = [
{ id: 1, pid: 0, name: "a" },
{ id: 2, pid: 1, name: "a_1" },
{ id: 3, pid: 1, name: "a_2" },
{ id: 4, pid: 2, name: "a_1_1" },
{ id: 5, pid: 3, name: "a_1_2" },
]
方法一:递归
const arrToTree = (arr, tree, pid) => {
arr.forEach((item) => {
// 判断是否为父级菜单
if (item.pid === pid) {
const child = {
...item,
children: [],
};
// 迭代 arr, 找到当前菜单相符合的所有子菜单
arrToTree(arr, child.children, item.id);
// 删掉不存在 children 值的属性
if (child.children.length <= 0) {
delete child.children;
}
// 加入到树中
tree.push(child);
}
});
};
// 调用函数时的参数为:1.原一维数组;2.最后获取的树状数组的引用;3.根id
let resTree = []
arrToTree(dataArr, resTree, 0)
方式二:reduce递归(时间复杂度同上,代码简洁一些)
const = (arr, pid) => {
return arr.reduce((res, current) => {
if (current['pid'] === pid) {
current.children = arrayToTree(arr, current['id']);
return res.concat(current);
}
return res;
}, []);
};
// 调用函数时的参数为:1.原一维数组;2.根id
let resTree = arrayToTree(data, 0);
上面两种方式可以获得一个一维数组转换为树形数据结构,但是有着一些缺陷:1.时间复杂度过高(O(n²)) 2.如果从根节点出发,其他数据节点没有形成一棵树的关系,那么最后只会返回根节点。
比如:
const data = [
{ id: 1, pid: 0, name: "a" },
{ id: 2, pid: 8, name: "a_1" },
{ id: 3, pid: 9, name: "a_2" },
{ id: 4, pid: 10, name: "a_1_1" },
{ id: 5, pid: 11, name: "a_1_2" },
];
// 方式一:
let resTree = []
arrToTree(dataArr, resTree, 0) // resTree只会为:[ { id: 1, pid: 0, name: 'a' } ]
// 方式二:
let resTree = arrayToTree(data, 0); // resTree只会为:[ { id: 1, pid: 0, name: 'a', children: [] } ]
方式三:map + 遍历
function arrayToTree(arr) {
let obj = {};
// obj对象的key为arr中每一个对象的id,value为每一个对象
arr.map((item) => {
obj[item.id] = item;
});
// 最终要返回的树型数组
let newArr = [];
// 对原一维数组进行遍历
for (let i = 0; i < arr.length; i++) {
let item = arr[i]; // 原一维数组中的每一项
let parent = obj[item.pid]; // 从之前保存的对象中取出当前项的父项
if (parent) {
if (parent.children) {
parent.children.push(item); // 父项的children加入子项
} else {
parent.children = [];
parent.children.push(item);
}
} else {
newArr.push(item); // 否则直接将当前项加入最后的树状数组作为根(因为此项没有父项)
}
}
return newArr;
};
let resTree = arrayToTree(data);
此时时间复杂度降为O(n),且没有父项的节点都会作为根节点