将含有层级联系的一维数组转为上下级依靠的树状目标

当开发进程中,遇到一组有层级联系的一维数组目标,需求将其处理为有上下级依靠联系的树状目标,遇到这个场景可以将本文的内容做参考运用。

场景示例

本文内容只合适处理像以下有明晰简明的数据结构,杂乱杂乱的结构是不支持的。

现在有一份一维数组数据,其间每项有一个属性代表着其级别(权重),其数据如下:

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":[
                    ]
                }
            ]
        }
    ]
}