将含有层级联系的一维数组转为上下级依靠的树状目标
当开发进程中,遇到一组有层级联系的一维数组目标,需求将其处理为有上下级依靠联系的树状目标,遇到这个场景可以将本文的内容做参考运用。
场景示例
本文内容只合适处理像以下有明晰简明的数据结构,杂乱杂乱的结构是不支持的。
现在有一份一维数组数据,其间每项有一个属性代表着其级别(权重),其数据如下:
const list = [
{
name: '目录',
weight: 1
},
{
name: '导航一',
weight: 2
},
{
name: '导航-路由一',
weight: 3
},
{
name: '导航-路由二',
weight: 3
},
{
name: '导航二',
weight: 2
},
{
name: '导航二-路由',
weight: 3
}
]
现在的诉求是,将list
数组转为树状目标,并能正确的表达其上下级联系。期望结果为:
const result = {
name: '目录',
weight: 1,
children: [
{
name: '导航一',
weight: 2,
children: [
{
name: '导航-路由一',
weight: 3,
children: []
},
{
name: '导航-路由二',
weight: 3,
children: []
},
]
},
{
name: '导航二',
weight: 2,
children: [
{
name: '导航二-路由',
weight: 3,
children: []
}
]
}
]
}
若是你也有上面场景需求,那就向下划拉划拉吧。
这个场景的运用举一个简单比方:
需求对md文件的h标签内容作为目录或者锚点信息运用。比方是怎么处理md的h标签转为右侧目录的。虽不知详细逻辑,但读取md内容,解析h标签内容,转为层级结构这个进程必定不会少的。当然这个进程也能运用一些md相关库辅佐快速完成。
接下来展开聊下详细思路与代码完成:
思路
首要分析这个list
,能直观感受到的便是weight
属性是破局的关键点。
但怎么能让它顺顺利利的听话,还需求设置一些规矩才行。
比方:
-
weight
在循环进程中若是遇到相同或小的值,需求结束循环,由于后边的就属于下一个联系了。 -
weight
需求在循环进程中有一个参考项,这个参考项告诉它是否越界了。
完好代码
// 联系解析器 - 适用于联系明晰的数组结构,而且只存在一个第一流,通常是第一项。
class RelationshipParser {
#dataSource; // 源数据
#result; // 处理的结果
/**
*
* @param {array} dataSource // 一维数组源数据, 没用ts编写的话,尽量校验下dataSource
*/
constructor(dataSource) {
this.#dataSource = JSON.parse(JSON.stringify(dataSource));
this.#init();
}
getResult() {
return JSON.parse(JSON.stringify(this.#result))
}
#init() {
const topLevelItem = this.#getTopLevel(); // 通常只要一个第一流
this.#parseData(this.#dataSource, topLevelItem);
this.#result = topLevelItem;
}
#getTopLevel() {
const topValue = Math.min(...this.#dataSource.map(item => item.weight));
return this.#dataSource.find(item => item.weight === topValue);
}
/**
* 递归解析当时dataSource,组成依靠联系树
* @param {array} dataSource // 源数据
* @param {object} currentItem // 当时节点,parseData函数处理的都是他的子级
*/
#parseData(dataSource, currentItem) {
currentItem.children = [] // 这个children便是容器,可以修正
// 开始索引,用于循环运用
const startIndex = dataSource.findIndex(item => item.weight === currentItem.weight);
// 当时权重,用于区分当时鸿沟
const currentWeight = currentItem.weight;
// 鸿沟,用于区分当时射中规模
let boundaryDepth = Number.MAX_SAFE_INTEGER;
// 这儿startIndex + 1作为开始索引,是为了只处理currentItem后边的数据
for (let index = startIndex + 1; index < dataSource.length; index++) {
const item = dataSource[index];
// 若当时权重小于等于入参权重,则跳出循环。
// 如 weigit:3 = weigit: 3, 阐明是同级
// 如 weigit:2 < weigit: 3, 阐明没有联系
// 如 weigit:4 > weigit: 3, 阐明是嵌套联系,继续向下处理
if (item.weight <= currentWeight) {
break;
}
// 若当时权重小于等于鸿沟权重,其实便是不是同一个权重就不处理
// 如 weigit:2 < weight: 10000,阐明是第一次射中,将当时项push到 currentItem 内
// 只要第一次是小于,后边只会处理等于,由于小于在上一拦截了,大于便是越界了不做处理
if (item.weight <= boundaryDepth) {
// 递归处理当时项的子级
this.#parseData(dataSource.slice(index), item);
// 将当时项push到currentItem
currentItem.children.push(item);
boundaryDepth = item.weight;
}
}
}
}
看下运用作用:
// 列表
const list = [
{
name: '目录',
weight: 1
},
{
name: '导航一',
weight: 2
},
{
name: '导航-路由一',
weight: 3
},
{
name: '导航-路由二',
weight: 3
},
{
name: '导航二',
weight: 2
},
{
name: '导航二-路由',
weight: 3
}
]
// 调用
const relationshipParser = new RelationshipParser(list);
console.log(relationshipParser.getResult());
// => 如下
{
"name":"目录",
"weight":1,
"children":[
{
"name":"导航一",
"weight":2,
"children":[
{
"name":"导航-路由一",
"weight":3,
"children":[
]
},
{
"name":"导航-路由二",
"weight":3,
"children":[
]
}
]
},
{
"name":"导航二",
"weight":2,
"children":[
{
"name":"导航二-路由",
"weight":3,
"children":[
]
}
]
}
]
}