场景
菜单节点数组如何组装成树形结构?
分析
树形结构一般为:
[
{
id: string,
pId: string,
children: [
{
id: string,
pId: string,
children: [...]
},
...
]
},
...
]
- 第一步:寻找根节点,默认根节点一般为
'-1' - 第二步:依据根节点作为父节点,递归添加子节点
实现
// 原始数据
const menu = [
{
name: "首页",
id: "1",
pId: "-1",
},
{
name: "新闻",
id: "2",
pId: "-1",
},
{
name: "国内新闻",
id: "3",
pId: "2",
},
{
name: "国际新闻",
id: "4",
pId: "2",
},
{
name: "体育",
id: "5",
pId: "-1",
},
{
name: "足球",
id: "6",
pId: "5",
},
{
name: "篮球",
id: "7",
pId: "5",
},
];
// 递归转换为树形结构
function deepFn(data, pId) {
// 查找当前pId下的所有子节点
const childData = data.filter((item) => item.pId === pId);
// 遍历子节点
childData.forEach((item) => {
// 递归调用
item.children = deepFn(data, item.id);
});
// 返回子节点
return childData;
}
const result = deepFn(menu, "-1");
扩展
实际场景中,可能存在以下情况:
- 根节点id不固定,可能是
0、null或其他 - 属性标识不一定是
id、pId、children这三种 - 转换树形结构的同时,可能会对当前子节点数据进行特殊处理
可将该动态变化的内容当做入参,封装一个统一的公共方法。
/**
* 将数组转换为树形结构
* @param {Array<object>} data 原始数据
* @param {object} options 配置项
* @params {string} options.parentId 父节点id
* @params {object} options.props 节点属性映射配置
* @params {string} options.props.id 节点id映射的属性key
* @params {string} options.props.pId 节点父id映射的属性key
* @params {string} options.props.children 子节点映射的属性key
* @param {Function} options.handle 处理节点数据的格式化函数
* @returns {Array<object>} 树形结构数据
*/
function shrinkFn(data, options = {}) {
const { parentId = "-1", props = { id: "id", pId: "pId", children: "children" }, handle } = options;
const { id: idKey, pId: pIdKey, children: childrenKey } = props;
// 递归转换为树形结构
function deepFn(data, pId) {
// 查找当前pId下的所有子节点
const childData = data.filter((item) => item[pIdKey] === pId);
// 遍历子节点
childData.forEach((item) => {
// 处理节点数据
if (typeof handle === "function") {
handle(item);
}
// 递归调用
item[childrenKey] = deepFn(data, item[idKey]);
});
// 返回子节点
return childData
}
return deepFn(data, parentId);
}
验证结果,给子节点添加isRoot属性
const result = shrinkFn(menu, {
parentId: "-1",
props: {
id: "id",
pId: "pId",
children: "children"
},
handle: (item) => {
item.isRoot = item.pId === "-1";
}
});
console.log(result)