案例需求
const arr = [
{ id: 'a', pid: '', name: '总裁办' },
{ id: 'b', pid: '', name: '行政部' },
{ id: 'c', pid: '', name: '财务部' },
{ id: 'd', pid: 'c', name: '财务核算部' },
{ id: 'e', pid: 'c', name: '税务管理部' }
];
把上面的数据转成下面的结果
const arr = [
{ id: 'a', pid: '', name: '总裁办' },
{ id: 'b', pid: '', name: '行政部' },
{
id: 'c',
pid: '',
name: '财务部',
children: [
{ id: 'd', pid: 'c', name: '财务核算部' },
{ id: 'e', pid: 'c', name: '税务管理部' }
],
},
];
递归实现与分析
const fn = (list, rootId) => {
const arr = [];
list.forEach(item => {
if (item.pid === rootId) {
const children = fn(list, item.id);
if (children.length) {
item.children = children;
}
arr.push(item);
}
});
return arr;
};
上面是代码实现,看上去挺简单,但对于递归接触少的同学理解起来还是有难度的,为了方便分析代码的执行过程,我把上面递归函数进行了打散,作用是一样的,如下:
const fn1 = (list, rootId) => {
const arr = [];
list.forEach(item => {
if (item.pid === rootId) {
const children = fn2(list, item.id);
if (children.length) {
item.children = children;
}
arr.push(item);
}
});
return arr;
};
const fn2 = (list, rootId) => {
const arr = [];
list.forEach(item => {
if (item.pid === rootId) {
const children = fn3(list, item.id);
if (children.length) {
item.children = children;
}
arr.push(item);
}
});
return arr;
};
const fn3 = (list, rootId) => {
const arr = [];
list.forEach(item => {
if (item.pid === rootId) {
// 代码执行不会用到 fn4,所以没有定义 fn4 也不会出错
const children = fn4(list, item.id);
if (children.length) {
item.children = children;
}
arr.push(item);
}
});
return arr;
};
调用 fn1 第 1 次循环
1、第 1 次调用 fn1,传递完整数据 list
,此时 rootId 为 “”
2、新建数组,循环 list,判断第 1 项的 pid 是否等于 “”,发现相等,进入 if
3、调用 fn2,传递完整数据 list
,rootId 为当前项的 id,就是 ‘a’
4、新建数组,循环 list,依次判断每一项的 pid 是否等于 ‘a’
5、发现没有 pid 等于 ‘a’,所以不会进入 if,等循环结束,返回空数组给 fn2,然后赋值给 children
6、此时 fn1 函数调用时的第 1 次循环,把 item 装进 arr
调用 fn1 第 2 次循环
1、循环 list,判断第 2 项的 pid 是否等于 “”,发现相等,进入 if
2、调用 fn2,传递完整数据 list
,rootId 为当前项的 id,就是 ‘b’
3、新建数组,循环 list,依次判断每一项的 pid 是否等于 ‘b’
4、发现没有 pid 等于 ‘b’,所以不会进入 if,等循环结束,返回空数组给 fn2,然后赋值给 children
5、此时 fn1 函数调用时的第 2 次循环,把 item 装进 arr
调用 fn1 第 3 次循环
1、循环 list,判断第 3 项的 pid 是否等于 “”,发现相等,进入 if
2、调用 fn2,传递完整数据 list
,rootId 为当前项的 id,就是 ‘c’
3、新建数组,循环 list,依次判断每一项的 pid 是否等于 ‘c’
4、发现第 4 项的 pid 等于 ‘c’,进入 if(注意调用 fn2 的循环还没有走完!)
5、调用 fn3,传递完整数据 list
,rootId 为当前项的 id,就是 ‘d’
6、新建数组,循环 list,依次判断每一项的 pid 是否等于 ‘d’
7、发现没有 pid 等于 ‘d’,所以不会进入 if,等循环结束,返回空数组给 fn3,然后赋值给 children
8、此时 fn2 函数调用时的第 4 次循环,把 item 装进 arr,注意这个 arr 是 fn2 函数中的 arr
9、继续,发现第 5 项的 pid 等于 ‘c’,进入 if
10、调用 fn3,传递完整数据 list
,rootId 为当前项的 id,就是 ‘e’
11、新建数组,循环 list,依次判断每一项的 pid 是否等于 ‘e’
12、发现没有 pid 等于 ‘e’,所以不会进入 if,等循环结束,返回空数组给 fn3,然后赋值给 children
13、此时 fn2 函数调用时的第 5 次循环,把 item 装进 arr,注意这个 arr 是 fn2 函数中的 arr
14、fn2 执行完毕返回的 arr 中就包含了最后 2 项,返回给 fn2 调用处
15、…
调用 fn1 第 4 次循环
继续循环到第 4 项、第 5 项的 pid 都不等于 “”,最终 fn1 函数返回前面收集好的 arr
其他方法
这个包 array-to-tree 也可以尝试下,比较简单!复杂场景请用 performant-array-to-tree
const arrayToTree = require('array-to-tree');
arrayToTree(arr, { parentProperty: 'pid', customID: 'id' }); // result is ok