一、为什么要用数组转树
在我们渲染后台数据时并不是所有的数据都是平铺直叙的,最典型的就是在后台管理平台上,一定会有的员工和部门的上下级管理关系,这是时候我们就需要使用数组转树形结构
二、完成数组转树形结构的方法
1. 性能不好,实现较为简单:递归方式
/**
* 方法一:简单递归
* @param { Array } data 数据源
* @param { Array } result 输出结果
* @param { Number | String } parentId 根id
*/
const getChildren = (data, result = [], parentId) => {
for (const item of data) {
if (item.parentId === parentId) {
const newItem = { ...item, children: [] };
result.push(newItem);
getChildren(data, newItem.children, item.id);
}
}
return result;
};
const res2 = getChildren(array, [], 0);
console.log("res2", res2);
/**
* 方法二:递归实现
* @param { Array } list 数组
* @param { String } parentId 父级 id
* @param { Object } param2 可配置参数
*/
const generateTree = (
list,
parentId = 0,
{ idName = "id", parentIdName = "parentId", childName = "children" } = {}
) => {
if (!Array.isArray(list)) {
throw new Error("type only Array");
// new Error("type only Array");
return list;
}
return list.reduce((pre, cur) => {
// 找到parentId 的子节点之后,递归找子节点的下一级节点
if (cur[parentIdName] === parentId) {
const children = generateTree(list, cur[idName]);
if (children.length) {
cur[childName] = children;
}
return [...pre, cur];
}
return pre;
}, []);
};
const result = generateTree(array, 0);
2. 性能可以,采用非递归方式
应用了对象保存的是引用的特点,每次将当前节点的 id 作为 key,保存对应节点的引用信息,遍历数组时,每次更新 objMap 的 children 信息,这样 objMap中保留了所有节点极其子节点,最重要的是,我们只需要遍历一遍数组。
/**
* 方法三:不用递归的简单循环
* @param { Array } 源数据
*/
const arrayToTree = (items) => {
const result = []; // 结果集
const itemMap = {};
// 先转成map存储
for (const item of items) {
itemMap[item.id] = { ...item, children: [] };
}
for (const item of items) {
const id = item.id;
const parentId = item.parentId;
const treeItem = itemMap[id];
if (parentId === 0) {
result.push(treeItem);
} else {
if (!itemMap[parentId]) {
itemMap[parentId] = { children: [] };
}
itemMap[parentId].children.push(treeItem);
}
}
return result;
};
const res3 = arrayToTree(array);
console.log("res3", res3);
/**
* 方法四:非递归实现 (映射 + 引用)
* 前提:每一项都有parentId,根元素
* @param { Array } list 数组
* @param { String } rootId 根元素Id
* @param { Object } param2 可配置参数
*/
const generateTree2 = (
list,
rootId = 0,
{ idName = "id", parentIdName = "parentId", childName = "childern" } = {}
) => {
if (!Array.isArray(list)) {
new Error("type only Array");
return list;
}
const objMap = {}; //暂存数组以 id 为 key的映射关系
const result = []; // 结果
for (const item of list) {
const id = item[idName];
const parentId = item[parentIdName];
// 该元素有可能已经放入map中,(找不到该项的parentId时 会先放入map
objMap[id] = !objMap[id] ? item : { ...item, ...objMap[id] };
const treeItem = objMap[id]; // 找到映射关系那一项(注意这里是引用)
if (parentId === rootId) {
// 已经到根元素则将映射结果放进结果集
result.push(treeItem);
} else {
// 若父元素不存在,初始化父元素
if (!objMap[parentId]) {
objMap[parentId] = [];
}
// 若无该根元素则放入map中
if (!objMap[parentId][childName]) {
objMap[parentId][childName] = [];
}
objMap[parentId][childName].push(treeItem);
}
}
return result;
};
const res = generateTree2(array);
console.log("res", res);
大佬原文地址1: # 面试了十几个高级前端,竟然连(扁平数据结构转Tree)都写不出来
大佬原文地址2: # 1w条数据,平铺数组转树形结构