
在前端开发中,我们经常需要处理具有复杂嵌套关系的数据结构,例如树形菜单、组织架构或多级分类列表。原始数据可能以扁平化或特定嵌套格式提供,但为了在ui组件(如ant design的tree组件)中展示或进行数据分析,我们需要将其转换为统一的递归树形结构。
本教程的目标是将以下这种包含 group 和 categories、subCategories 的嵌套数组:
const arr = [
{
group: { id: "group1", groupname: "groupname1" },
categories: [
{
id: "cat1",
categoryName: "category1",
total: 5,
available: 2,
subCategories: []
},
{
id: "cat2",
categoryName: "category2",
total: 15,
available: 12,
subCategories: [
{
id: "cat3",
categoryName: "category3",
total: 15,
available: 12,
subCategories: []
}
]
}
]
},
{
group: { id: "group2", groupname: "groupname2" },
categories: [
{
id: "cat4",
categoryName: "category4",
total: 25,
available: 22,
subCategories: []
},
{
id: "cat5",
categoryName: "category5",
total: 50,
available: 25,
subCategories: []
}
]
}
];转换为以下统一的树形结构,其中每个节点都包含 key, name, total, available 和 children 属性。特别地,顶层节点(如 group1, group2)的 total 和 available 属性需要聚合其所有子节点的相应值:
[
{
"key": "group1",
"name": "groupname1",
"total": 35, // 5 + 15 + 15 (cat1.total + cat2.total + cat3.total)
"available": 26, // 2 + 12 + 12 (cat1.available + cat2.available + cat3.available)
"children": [
{
"key": "cat1",
"name": "category1",
"total": 5,
"available": 2,
"children": []
},
{
"key": "cat2",
"name": "category2",
"total": 30, // 15 + 15 (cat2.total + cat3.total)
"available": 24, // 12 + 12 (cat2.available + cat3.available)
"children": [
{
"key": "cat3",
"name": "category3",
"total": 15,
"available": 12,
"children": []
}
]
}
]
},
// ... 其他组
](注:根据原始问题,group1 的 total 和 available 应该聚合其直接子节点及其孙子节点的所有值。例如,cat2 的 total 应为 15 + 15 = 30,group1 的 total 应为 5 + 30 = 35。原始问题中的期望输出 total: 20, available: 24 可能是一个笔误,或者只考虑了直接子节点。本教程将实现聚合所有子孙节点的功能。)
首先,我们需要一个递归函数来遍历原始数组,并将其中的 group、category 和 subCategory 对象转换为目标格式的节点。这个函数将负责映射 id 到 key,groupname/categoryName 到 name,并处理 categories/subCategories 到 children 的转换。
立即学习“Java免费学习笔记(深入)”;
const formatter = (data) => {
// 递归处理单个项目,将其转换为目标结构
const recursiveTree = (item) => {
if (item.group) { // 处理顶层group项目
const {
group: { id, groupname }, // 提取group的id和名称
categories
} = item;
return {
key: id,
name: groupname,
total: 0, // 初始设置为0,待后续聚合
available: 0, // 初始设置为0,待后续聚合
children: categories?.map(recursiveTree) || [] // 递归处理子分类
};
} else { // 处理category或subCategory项目
const { id, categoryName, total, available, subCategories } = item;
// 对于category/subCategory,其自身的total和available是已知的
const children = subCategories?.map(recursiveTree) || [];
// 如果category/subCategory有子节点,其total和available也需要聚合
const aggregatedTotal = children.reduce((sum, child) => sum + child.total, total || 0);
const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, available || 0);
return {
key: id,
name: categoryName,
total: aggregatedTotal,
available: aggregatedAvailable,
children: children
};
}
};
// 遍历原始数据数组,应用递归转换
return data.map(recursiveTree);
};代码解析:
上述 recursiveTree 函数已经能够处理 category 和 subCategory 自身的聚合,但对于最顶层的 group 节点,由于其 total 和 available 是从其 categories 聚合而来,而 categories 的聚合又依赖于其 subCategories,这种“自下而上”的计算最好在所有子节点都处理完毕后进行。
一种高效的方法是在 formatter 函数的最后,对已经完成结构转换的顶层节点进行一次后处理。
const formatterWithAggregation = (data) => {
// 递归处理单个项目,将其转换为目标结构并计算自身及子孙节点的聚合值
const recursiveTree = (item) => {
if (item.group) { // 处理顶层group项目
const {
group: { id, groupname },
categories
} = item;
// 递归处理所有子分类
const children = categories?.map(recursiveTree) || [];
// 对于group节点,其total和available是所有子孙节点的聚合
const aggregatedTotal = children.reduce((sum, child) => sum + child.total, 0);
const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, 0);
return {
key: id,
name: groupname,
total: aggregatedTotal,
available: aggregatedAvailable,
children: children
};
} else { // 处理category或subCategory项目
const { id, categoryName, total, available, subCategories } = item;
const children = subCategories?.map(recursiveTree) || [];
// 对于category/subCategory,其自身的total和available是自身值与子孙值的聚合
const aggregatedTotal = children.reduce((sum, child) => sum + child.total, total || 0);
const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, available || 0);
return {
key: id,
name: categoryName,
total: aggregatedTotal,
available: aggregatedAvailable,
children: children
};
}
};
// 直接在map过程中完成所有层级的聚合计算
return data.map(recursiveTree);
};优化说明:
在第一次的代码实现中,group 节点的 total 和 available 被初始化为 0,而 category 节点的 total 和 available 则在递归返回时进行了聚合。实际上,我们可以将聚合逻辑统一到 recursiveTree 函数内部,在每次递归返回时,都确保当前节点的 total 和 available 已经包含了其所有子孙节点的聚合值。这样,当 group 节点处理其 children (即 categories) 并得到它们已经聚合好的 total 和 available 后,就可以直接进行求和。
这种“自底向上”的递归聚合方法更为简洁和高效,因为它避免了额外的后处理循环。
将上述逻辑整合,得到最终的解决方案:
const arr = [
{
group: { id: "group1", groupname: "groupname1" },
categories: [
{
id: "cat1",
categoryName: "category1",
total: 5,
available: 2,
subCategories: []
},
{
id: "cat2",
categoryName: "category2",
total: 15,
available: 12,
subCategories: [
{
id: "cat3",
categoryName: "category3",
total: 15,
available: 12,
subCategories: []
}
]
}
]
},
{
group: { id: "group2", groupname: "groupname2" },
categories: [
{
id: "cat4",
categoryName: "category4",
total: 25,
available: 22,
subCategories: []
},
{
id: "cat5",
categoryName: "category5",
total: 50,
available: 25,
subCategories: []
}
]
}
];
const formatter = (data) => {
// 递归处理单个项目,将其转换为目标结构并计算自身及子孙节点的聚合值
const recursiveTree = (item) => {
if (item.group) { // 处理顶层group项目
const {
group: { id, groupname },
categories
} = item;
// 递归处理所有子分类
const children = categories?.map(recursiveTree) || [];
// 对于group节点,其total和available是所有子孙节点的聚合
const aggregatedTotal = children.reduce((sum, child) => sum + child.total, 0);
const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, 0);
return {
key: id,
name: groupname,
total: aggregatedTotal,
available: aggregatedAvailable,
children: children
};
} else { // 处理category或subCategory项目
const { id, categoryName, total, available, subCategories } = item;
const children = subCategories?.map(recursiveTree) || [];
// 对于category/subCategory,其自身的total和available是自身值与子孙值的聚合
// 注意:这里total和available的初始值是当前item自身的total/available
const aggregatedTotal = children.reduce((sum, child) => sum + child.total, total || 0);
const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, available || 0);
return {
key: id,
name: categoryName,
total: aggregatedTotal,
available: aggregatedAvailable,
children: children
};
}
};
// 直接在map过程中完成所有层级的聚合计算
return data.map(recursiveTree);
};
const result = formatter(arr);
console.log(JSON.stringify(result, null, 2));本教程通过一个两阶段(但最终优化为一步到位)的递归处理方法,成功地将复杂的嵌套数组结构转换为统一的树形结构,并实现了父节点对其所有子孙节点数值型数据的聚合计算。核心在于利用递归的“自底向上”特性,在每次递归调用返回时,确保当前节点的聚合值已经计算完毕,从而使得上层节点可以直接使用这些聚合值进行进一步的汇总。这种模式在处理各种树形数据结构转换和数据汇总的场景中具有广泛的应用价值。
以上就是JavaScript递归数组结构转换与父节点数据聚合计算的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号