树形多级菜单数据源嵌套结构与扁平结构互转
数据源格式
一般来说,要想动态渲染树形菜单,这个样子的数据源格式对前端很友好。
var data = [
{
name: "父节点1",
children: [
{
name: "子节点11",
children:[
{
name: "叶子节点111",
children:[]
},
{
name: "叶子节点112",
children:[]
},
{
name: "叶子节点113",
children:[]
},
{
name: "叶子节点114",
children:[]
}
]
}
//...
]
},
];
问题
在后端,这些数据通常是存储在关系型数据库中,后端开发将数据从数据库中取出来返回给前端的数据往往这样子的:
const data =[
{ id:1, pid:0, name:"父节点1" },
{ id:11, pid:1, name:"父节点11" },
{ id:111, pid:11, name:"叶子节点111" },
{ id:112, pid:11, name:"叶子节点112" },
{ id:113, pid:11, name:"叶子节点113" },
{ id:114, pid:11, name:"叶子节点114" },
{ id:12, pid:1, name:"父节点12" },
{ id:121, pid:12, name:"叶子节点121" },
{ id:122, pid:12, name:"叶子节点122" },
{ id:123, pid:12, name:"叶子节点123" },
{ id:124, pid:12, name:"叶子节点124" },
{ id:13, pid:1, name:"父节点13" },
{ id:2, pid:0, name:"父节点2" },
{ id:21, pid:2, name:"父节点21" },
{ id:211, pid:21, name:"叶子节点211" },
{ id:212, pid:21, name:"叶子节点212" },
{ id:213, pid:21, name:"叶子节点213" },
{ id:214, pid:21, name:"叶子节点214" },
{ id:22, pid:2, name:"父节点22" },
{ id:221, pid:22, name:"叶子节点221" },
{ id:222, pid:22, name:"叶子节点222" },
{ id:223, pid:22, name:"叶子节点223" },
{ id:224, pid:22, name:"叶子节点224" },
{ id:23, pid:2, name:"父节点23" },
{ id:231, pid:23, name:"叶子节点231" },
{ id:232, pid:23, name:"叶子节点232" },
{ id:233, pid:23, name:"叶子节点233" },
{ id:234, pid:23, name:"叶子节点234" },
{ id:3, pid:0, name:"父节点3" }
];
解决方案
扁平转嵌套
/**
* 将一个普通的节点数组(带有指向父节点的指针)转换为嵌套的数据结构。
* @param {*} data 一组数据
* @param {*} option 包含以下字段的对象:
* parentProperty(String):可以找到父节点链接的属性的名称。默认值:'pid'。
* childrenProperty(String):将存储子节点的属性的名称。默认值:'children'。
* idProperty(String):唯一的节点标识符。默认值:'id'。
* nameProperty(String):节点的名称。默认值:'name'。
*/
function FlatToNested(data, option) {
option = option || {};
let idProperty = option.idProperty || 'id';
let parentProperty = option.parentProperty || 'pid';
let childrenProperty = option.childrenProperty || 'children';
let res = [],
tmpMap = [];
for (let i = 0; i < data.length; i++) {
tmpMap[data[i][idProperty]] = data[i];
if (tmpMap[data[i][parentProperty]] && data[i][idProperty] != data[i][parentProperty]) {
if (!tmpMap[data[i][parentProperty]][childrenProperty])
tmpMap[data[i][parentProperty]][childrenProperty] = [];
tmpMap[data[i][parentProperty]][childrenProperty].push(data[i]);
} else {
res.push(data[i]);
}
}
return res;
}
如果返回的数据和示例数据的字段不一致,那么您也无需更改源代码,方法提供了可配置选项,如下所示:
例如,您收到这样的数据:
const data =[
{ _id:1, parentID:0, text:"父节点1" },
{ _id:11, parentID:1, text:"父节点11" },
{ _id:111, parentID:11, text:"叶子节点111" },
{ _id:112, parentID:11, text:"叶子节点112" },
{ _id:113, parentID:11, text:"叶子节点113" },
{ _id:114, parentID:11, text:"叶子节点114" },
{ _id:12, parentID:1, text:"父节点12" },
{ _id:121, parentID:12, text:"叶子节点121" },
{ _id:122, parentID:12, text:"叶子节点122" }
//...
];
那么,您可以这样调用函数:
FlatToNested(nodes,{
idProperty:'_id', //唯一的节点标识符。
nameProperty:'text', //节点的名称。
parentProperty:'parentID', //可以找到父节点链接的属性的名称。
childrenProperty:'son' //将存储子节点的属性的名称。
})
### 嵌套转扁平
```javascript
/**
* 嵌套型格式转扁平型格式
* @param {Array} data
*/
function NestedToFlat(data,pid) {
var res = []
for (var i = 0; i < data.length; i++) {
res.push({
id: data[i].id,
name: data[i].name,
pid: pid || 0
})
if (data[i].children) {
res = res.concat(NestedToFlat(data[i].children, data[i].id));
}
}
return res;
}