在日常的代码生活中,经常会有列表转换树状的需求,一个树状的渲染,后端只给一个列表结构数据
const items = [
{ id: 1 },
{ id: 2, pid: 1 },
{ id: 3, pid: 2 }
];
但是我们需要的结构是树状的
[
{
"id": 1,
"children": [
{
"id": 2,
"children": [
{
"id": 3
}
]
}
]
}
]
当需要转换成树形结构时,我们可以使用递归算法来构建树。以下是一个将结构 [ {id:1}, {id:2, pid: 1}, {id:3, pid: 2} ]
转换为树形结构的 JavaScript 实现:
function toTree(list, parent) {
const tree = [];
const len = list.length;
for (let i = 0; i < len; i++) {
if (list[i].pid === parent) {
const node = {
id: list[i].id
};
const children = toTree(list, list[i].id);
if (children.length) {
node.children = children;
}
tree.push(node);
}
}
return tree;
}
const items = [
{ id: 1 },
{ id: 2, pid: 1 },
{ id: 3, pid: 2 }
];
const tree = toTree(items, undefined);
console.log(JSON.stringify(tree, null, 4));
在上面的代码中,我们定义了一个名为 toTree()
的函数,该函数接收一个数组 list
和一个可选的父元素 parent
。如果不传入 parent
参数,则默认为 undefined
,表示树的根节点。
在 toTree()
函数中,我们遍历 list
数组,如果某个节点的 pid
属性与当前正在遍历的父元素的 id
匹配,则递归调用 toTree()
函数,将当前节点作为父元素,获得该节点的子树。
最后,我们在 toTree()
函数中返回树形结构。
哪,还有没有性能高一点的方法呢
如果数据量比较大,递归调用的嵌套深度会比较大,容易导致栈溢出等问题。如果要处理大量数据,建议使用迭代方式来构建树形结构,可以提高运行效率。
以下是一个使用迭代方式将数组转换为树形结构的 JavaScript 实现:
function toTree(list, rootId) {
const tree = [];
const itemMap = new Map();
for (const item of list) {
item.children = [];
itemMap.set(item.id, item);
}
for (const item of list) {
const parent = itemMap.get(item.pid);
if (parent) {
parent.children.push(item);
} else if (item.pid === rootId) {
tree.push(item);
}
}
return tree;
}
const items = [
{ id: 1 },
{ id: 2, pid: 1 },
{ id: 3, pid: 2 },
{ id: 4, pid: 2 },
{ id: 5, pid: 4 },
{ id: 6, pid: 4 },
{ id: 7, pid: 1 },
{ id: 8, pid: 7 },
{ id: 9, pid: 8 },
{ id: 10, pid: 8 },
{ id: 11, pid: 7 },
{ id: 12, pid: 11 },
{ id: 13, pid: 12 },
{ id: 14, pid: 11 },
{ id: 15, pid: 14 },
{ id: 16, pid: 15 }
];
const tree = toTree(items);
console.log(JSON.stringify(tree,null, 4));
在上面的代码中,我们定义了一个名为 toTree()
的函数,该函数接收一个数组 list
和一个可选的根节点 rootId
。如果不传入 rootId
参数,则默认为 undefined
,表示树的根节点为 id 为 1 的节点。
在 toTree()
函数中,我们首先通过遍历 list
数组为每个节点添加一个 children
属性,表示该节点的子节点。然后,使用一个 Map 对象(itemMap
)来存储每个节点,以 id
属性为键,以整个节点对象为值。
接下来,我们再次遍历 list
数组,并在 Map 对象 itemMap
中查找每个节点的父节点。如果找到了父节点,就将节点添加到父节点的 children
属性中;否则,如果该节点的 pid
属性等于 rootId
(默认为 1),则将该节点添加到树形结构中。
最后,返回树形结构。
这种方式可以大大提高性能,并且避免了递归调用时的栈溢出等问题。
下面还有一个TypeScript使用forEach版本,同样可以实现列表转换树状结构
interface I_BaseTreeData {
id: string;
pid: string;
children: [];
}
/**
* 列表结构转树状结构
* @param list
* @returns
*/
export function listToTree(list: any[]) {
let tree = <any>[];
let tmp = {};
list.forEach((item: I_BaseTreeData) => {
tmp[item.id] = item;
if (item.children === undefined) {
item.children = [];
}
});
list.forEach((item: I_BaseTreeData) => {
if (tmp[item.pid] === undefined) {
tree.push(item);
} else {
tmp[item.pid].children.push(item);
}
})
return tree;
}