我们在项目中,经常会遇到树形结构功能,比如部门、分类、菜单权限等模块。经常会遇到list转树形结构、树形结构转list
当列表中有多个根节点时,将其转换为树形结构并不适合使用递归,因为递归需要遍历每个节点,并且每次递归调用都需要创建一个新的函数调用帧,这可能会导致栈溢出或内存占用过高。
一种更有效的方法是使用迭代和哈希表来遍历列表,并在哈希表中保存每个节点的引用。以下是一个 TypeScript 函数,它将包含 id
、parentId
、name
属性的列表转换为树形结构,支持多个根节点:
/**
* 声明一个树形结构接口
*/
interface TreeNode {
id: number;
children?: TreeNode[];
parentId: number;
name: string;
}
/**
* 后端返回list数据接口
*/
interface ListItem {
id: number;
parentId: number;
name: string;
}
/**
* list转树形结构方法
* @param items
* @returns
*/
function listToTree(items: ListItem[]): TreeNode[] {
/**
* 定义一个树形节点数组
*/
const result: TreeNode[] = [];
/**
* 定义map key是ListItem 数组对象元素id
* value 是ListItem 数组对象元素
*/
const itemMap: Record<number, TreeNode> = {};
// 遍历数组所有数组,把id当作key,把item当作valu put到 itemMap里面
for (const item of items) {
/**
* 对象结构
* 将 item 对象的属性浅拷贝到一个新对象 node 中,并将 node 对象的 children 属性设置为空数组。
*/
const node: TreeNode = { ...item, children: [] };
itemMap[item.id] = node;
}
/**
* 遍历数组,找到它的父类
*/
for (const item of items) {
const node = itemMap[item.id];
/**
* 判断当前元素是否是根节点
*/
if (item.parentId === 0) {
result.push(node);
} else {
/**
* 不是跟节点,通过map找他的父节点
* 把元素添加到children
*/
const parent = itemMap[item.parentId];
parent.children?.push(node);
}
}
return result;
}
/**
* mock
* 模拟后端返回的数据
*/
const items: ListItem[] = [
{ id: 1, parentId: 0, name: '0-1'},
{ id: 2, parentId: 1, name: '1-2'},
{ id: 3, parentId: 1, name: '1-3'},
{ id: 4, parentId: 2, name: '2-4'},
{ id: 5, parentId: 2, name: '2-5'},
{ id: 6, parentId: 3, name: '3-6'},
{ id: 7, parentId: 0, name: '0-7'},
{ id: 8, parentId: 7, name: '7-8'},
{ id: 9, parentId: 7, name: '7-9'},
{ id: 10, parentId: 8, name: '8-10'},
{ id: 11, parentId: 8, name: '8-11'},
{ id: 12, parentId: 9, name: '9-12'}
];
const tree = listToTree(items);
console.log(tree);
输出结果:
[
{
"children": [
{
"children": [
{
"children": [],
"id": 4,
"name": "2-4",
"parentId": 2
},
{
"children": [],
"id": 5,
"name": "2-5",
"parentId": 2
}
],
"id": 2,
"name": "1-2",
"parentId": 1
},
{
"children": [
{
"children": [],
"id": 6,
"name": "3-6",
"parentId": 3
}
],
"id": 3,
"name": "1-3",
"parentId": 1
}
],
"id": 1,
"name": "0-1",
"parentId": 0
},
{
"children": [
{
"children": [
{
"children": [],
"id": 10,
"name": "8-10",
"parentId": 8
},
{
"children": [],
"id": 11,
"name": "8-11",
"parentId": 8
}
],
"id": 8,
"name": "7-8",
"parentId": 7
},
{
"children": [
{
"children": [],
"id": 12,
"name": "9-12",
"parentId": 9
}
],
"id": 9,
"name": "7-9",
"parentId": 7
}
],
"id": 7,
"name": "0-7",
"parentId": 0
}
]
如果想把它封装成一个通用的结构可以这样玩,通过动态传入树关联字段,从而获取对应的值
function arrayToTree<T>(
items: T[],
options: {
idKey: string;
parentKey: string;
nameKey: string;
}
): TreeNode[] {
const { idKey, parentKey,nameKey } = options;
console.log(idKey,parentKey,nameKey)
const result: TreeNode[] = [];
const itemMap: Record<string, TreeNode> = {};
for (const item of items) {
const node:TreeNode ={
uid:item[idKey],
pid:item[parentKey],
name:item[nameKey],
children:[]
}
itemMap[item[idKey]] = node;
}
console.log(itemMap)
for (const key in itemMap) {
const node=itemMap[key]
if(node.pid === '0'){
result.push(node)
}else{
const parent=itemMap[node.pid]
if(parent){
parent.children?.push(node)
}
}
}
return result;
}
/**
* 声明一个树形结构接口
*/
interface TreeNode {
uid: string;
children?: TreeNode[];
pid: string;
name: string;
}
interface ListData{
id:string;
parentId:string;
name:string;
}
// 例子用法
const data:ListData[] = [
{ id: '1', parentId: '0', name: 'Node 1' },
{ id: '2', parentId: '1', name: 'Node 1.1' },
{ id: '3', parentId: '1', name: 'Node 1.2' },
{ id: '4', parentId: '2', name: 'Node 1.1.1' },
];
const tree = arrayToTree<ListData>(data, {
idKey: 'id',
parentKey: 'parentId',
nameKey:'name'
});
console.log(JSON.stringify(tree));