# 1、入门级方法
function transformToTree(data) {
// 创建一个空对象用于存储键值对形式的节点映射
const nodeMap = {};
// 遍历原始数据,构建节点映射表并初始化节点的 children 属性
data.forEach(item => {
const node = { id: item.id, name: item.name, children: [] };
nodeMap[item.id] = node;
if (item.pid !== 0) {
// 如果当前节点不是根节点,则将其添加到其父节点的 children 数组中
if (nodeMap[item.pid]) {
nodeMap[item.pid].children.push(node);
}
}
});
// 从映射表中提取所有 pid 为 0 的根节点
return Object.values(nodeMap).filter(node => node.pid === 0);
}
// 使用给定的数据进行转换
const menuData = [
{ id:1 , name:"关于我们" , pid:0 },
{ id:2 , name:"公司介绍" , pid:1 },
{ id:3 , name:"公司动态" , pid:1 },
{ id:4 , name:"公司团建" , pid:1 },
{ id:5 , name:"公司简介" , pid:2 },
{ id:6 , name:"企业资质" , pid:2 },
{ id:7 , name:"企业文化" , pid:2 },
{ id:8 , name:"发展历程" , pid:2 },
];
const treeData = transformToTree(menuData);
console.log(treeData);
说明:
上述代码首先创建了一个节点映射表,然后遍历原始数据,构建了每个节点以及它们之间的父子关系。最后,从映射表中提取出所有父节点ID为0的根节点作为最终的树形结构。运行此代码后,您将得到按照层级关系组织的树形菜单数据。
# 2、增加一个可选的参数 rootPid 来指定哪个 pid 应被视为根节点
为了增强该方法的灵活性,我们可以增加一个可选的参数 rootPid 来指定哪个 pid 应被视为根节点。默认情况下,如果未传递 rootPid 参数,则视为根节点的 pid 为 0
function transformToTree(data, rootPid = 0) {
const nodeMap = {};
data.forEach(item => {
const node = { id: item.id, name: item.name, children: [] };
nodeMap[item.id] = node;
if (item.pid !== rootPid) {
if (nodeMap[item.pid]) {
nodeMap[item.pid].children.push(node);
}
}
});
return Object.values(nodeMap).filter(node => node.pid === rootPid);
}
// 示例数据
const menuData = [
{ id:1 , name:"关于我们" , pid:0 },
{ id:2 , name:"公司介绍" , pid:1 },
{ id:3 , name:"公司动态" , pid:1 },
{ id:4 , name:"公司团建" , pid:1 },
{ id:5 , name:"公司简介" , pid:2 },
{ id:6 , name:"企业资质" , pid:2 },
{ id:7 , name:"企业文化" , pid:2 },
{ id:8 , name:"发展历程" , pid:2 },
];
// 默认情况,rootPid 为 0
const defaultTreeData = transformToTree(menuData);
// 自定义 rootPid 为其他值时的示例
const customTreeData = transformToTree(menuData, 1); // 此处以 pid 为 1 的节点作为树的根节点
console.log(defaultTreeData);
console.log(customTreeData);
说明:
transformToTree方法接受一个可选的 rootPid 参数,可以根据需要灵活地指定树形结构的根节点。
# 3、ES6 的解构赋值特性对方法进行优化
为了让代码更加简洁和自动化,您可以利用 ES6 的解构赋值特性来根据 menuData 数据项的属性自动抽取对应的值来创建 node 对象。下面是优化后的代码:
function transformToTree(data, rootPid = 0) {
const nodeMap = {};
data.forEach(({ id, name, pid }) => {
const node = { id, name, children: [] };
nodeMap[id] = node;
if (pid !== rootPid) {
if (nodeMap[pid]) {
nodeMap[pid].children.push(node);
}
}
});
return Object.values(nodeMap).filter(node => node.pid === rootPid);
}
// 示例数据
const menuData = [
{ id:1 , name:"关于我们" , pid:0 },
{ id:2 , name:"公司介绍" , pid:1 },
{ id:3 , name:"公司动态" , pid:1 },
{ id:4 , name:"公司团建" , pid:1 },
{ id:5 , name:"公司简介" , pid:2 },
{ id:6 , name:"企业资质" , pid:2 },
{ id:7 , name:"企业文化" , pid:2 },
{ id:8 , name:"发展历程" , pid:2 },
];
// 默认情况,rootPid 为 0
const defaultTreeData = transformToTree(menuData);
// 自定义 rootPid 为其他值时的示例
const customTreeData = transformToTree(menuData, 1);
console.log(defaultTreeData);
console.log(customTreeData);
说明:
这样,在 forEach 循环内部直接通过解构赋值的方式抽取了每个元素的 id、name 和 pid 属性,使得代码更具通用性和易于维护。如果将来数据结构有所调整,只需更改 menuData 中的对象结构即可,无需改动 transformToTree 函数内部逻辑。
# 4、适配提供的数据含有其他属性的情况
//如:原始数据如下
[
{ id:1 , name:"关于我们" , pid:0 },
{ id:2 , name:"公司介绍" , pid:1 , enname:"about us"},
{ id:3 , name:"公司动态" , pid:1 },
{ id:4 , name:"公司团建" , pid:1 },
{ id:5 , name:"公司简介" , pid:2 },
{ id:6 , name:"企业资质" , pid:2 },
{ id:7 , name:"企业文化" , pid:2 },
{ id:8 , name:"发展历程" , pid:2 },
]
// 针对新增的属性字段,例如这里的 enname,我们可以保持节点对象的构造方式不变,仅保留必要的属性和新的自定义属性。这样当数据中有其他属性时,仍然能够正确构建树形结构,同时保留所有原数据中的属性。以下是更新后的代码:
function transformToTree(data, rootPid = 0) {
const nodeMap = {};
data.forEach(({ id, name, pid, ...otherProps }) => {
const node = { id, name, pid, children: [], ...otherProps };
nodeMap[id] = node;
if (pid !== rootPid && nodeMap[pid]) {
nodeMap[pid].children.push(node);
}
});
return Object.values(nodeMap).filter(node => node.pid === rootPid);
}
// 示例数据
const menuData = [
{ id:1 , name:"关于我们" , pid:0 },
{ id:2 , name:"公司介绍" , pid:1 , enname:"about us"},
{ id:3 , name:"公司动态" , pid:1 },
{ id:4 , name:"公司团建" , pid:1 },
{ id:5 , name:"公司简介" , pid:2 },
{ id:6 , name:"企业资质" , pid:2 },
{ id:7 , name:"企业文化" , pid:2 },
{ id:8 , name:"发展历程" , pid:2 },
];
// 默认情况,rootPid 为 0
const treeData = transformToTree(menuData);
console.log(treeData);
说明:
在此代码中,我们使用了扩展运算符 (...) 将除 id、name、pid 之外的所有其他属性合并到新创建的 node 对象中,这样即使后续有更多额外属性也能被正确包含在内。
# 5、在4的返回的树形结构结果上,自动加上一个层级属性level
为了在返回的树形结构结果上自动加上一个表示层级的 level 属性,我们需要遍历时跟踪每个节点所在的层级,并将其添加到相应的节点对象中。以下是在原有基础上增加层级属性的方法:
function transformToTree(data, rootPid = 0, level = 0) {
const nodeMap = {};
data.forEach(({ id, name, pid, ...otherProps }) => {
const node = { id, name, pid, level, children: [], ...otherProps };
nodeMap[id] = node;
if (pid !== rootPid && nodeMap[pid]) {
nodeMap[pid].children.push(node);
// 更新子节点的层级为父节点层级 + 1
node.level = nodeMap[pid].level + 1;
} else if (pid === rootPid) {
// 根节点层级为 0 或者自定义初始层级
node.level = level;
}
});
return Object.values(nodeMap).filter(node => node.pid === rootPid);
}
// 示例数据
const menuData = [
{ id:1 , name:"关于我们" , pid:0 },
{ id:2 , name:"公司介绍" , pid:1 , enname:"about us"},
{ id:3 , name:"公司动态" , pid:1 },
{ id:4 , name:"公司团建" , pid:1 },
{ id:5 , name:"公司简介" , pid:2 },
{ id:6 , name:"企业资质" , pid:2 },
{ id:7 , name:"企业文化" , pid:2 },
{ id:8 , name:"发展历程" , pid:2 },
];
// 默认情况,rootPid 为 0,层级为 0
const treeData = transformToTree(menuData);
console.log(treeData);
说明:
在这个实现中,我们在遍历数据构建树的同时,计算并设置了每个节点的 level 属性,确保了每个节点的层级信息准确反映在树形结构中。
# 6、演变最终方法:兼容性和扩展性更好的方法(对更为原始的数据进行处理,只包含节点id,和父节点pid)
//如:原始数据如下
[
{ id:1 , pid:0 },
{ id:2 , pid:1,enname:"about us" },
{ id:3 , pid:1 },
{ id:4 , pid:1 },
{ id:5 , pid:2 },
{ id:6 , pid:2 },
{ id:7 , pid:2 },
{ id:8 , pid:2 },
]
// 考虑到提供的最新数据结构中移除了 name 字段,假设您希望在转换过程中依然保留原有的功能,我们可以设定一个默认的 name 值(例如“未命名”)来填充缺失的 name,然后再进行树形结构的构建。以下是相应的方法改写:
function transformToTree(data, rootPid = 0, level = 0, defaultName = "未命名") {
const nodeMap = {};
data.forEach(({ id, pid, enname, ...otherProps }) => {
let name = enname || defaultName;
const node = { id, name, pid, level, children: [], ...otherProps };
nodeMap[id] = node;
if (pid !== rootPid && nodeMap[pid]) {
nodeMap[pid].children.push(node);
node.level = nodeMap[pid].level + 1;
} else if (pid === rootPid) {
node.level = level;
}
});
return Object.values(nodeMap).filter(node => node.pid === rootPid);
}
// 示例数据
const menuData = [
{ id:1 , pid:0 },
{ id:2 , pid:1, enname:"about us" },
{ id:3 , pid:1 },
{ id:4 , pid:1 },
{ id:5 , pid:2 },
{ id:6 , pid:2 },
{ id:7 , pid:2 },
{ id:8 , pid:2 },
];
// 默认情况,rootPid 为 0,层级为 0,默认名称为 "未命名"
const treeData = transformToTree(menuData);
console.log(treeData);
说明:
在这段代码中,我们检查了每条记录是否存在 enname 字段,如果有则将其用作 name,否则使用默认名称 "未命名"。同时,之前添加层级属性的功能也得到了保留。
# 7、结果展示
//测试
//测试树形结构
const menuData = [
{ id: 1, name: "关于我们", pid: 0 },
{ id: 2, name: "公司介绍", pid: 1 , enname:"about us"},
{ id: 3, name: "公司动态", pid: 1 },
{ id: 4, name: "公司团建", pid: 1 },
{ id: 5, name: "公司简介", pid: 2 },
{ id: 6, name: "企业资质", pid: 2 },
{ id: 7, name: "企业文化", pid: 2 },
{ id: 8, name: "发展历程", pid: 2 },
];
// 默认情况,rootPid 为 0,层级为 0,默认名称为 "未命名"
const res = app.transformToTree(menuData);
console.log('测试树形结构', JSON.stringify(res));
//结果展示:
[
{
"id": 1,
"name": "关于我们",
"pid": 0,
"level": 0,
"children": [
{
"id": 2,
"name": "公司介绍",
"pid": 1,
"level": 1,
"enname":"about us",
"children": [
{
"id": 5,
"name": "公司简介",
"pid": 2,
"level": 2,
"children": []
},
{
"id": 6,
"name": "企业资质",
"pid": 2,
"level": 2,
"children": []
},
{
"id": 7,
"name": "企业文化",
"pid": 2,
"level": 2,
"children": []
},
{
"id": 8,
"name": "发展历程",
"pid": 2,
"level": 2,
"children": []
}
]
},
{
"id": 3,
"name": "公司动态",
"pid": 1,
"level": 1,
"children": []
},
{
"id": 4,
"name": "公司团建",
"pid": 1,
"level": 1,
"children": []
}]
}
]