菜单数组如何转换为树形结构

101 阅读2分钟

场景

菜单节点数组如何组装成树形结构?

分析

树形结构一般为:

[
    {
        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");

扩展

实际场景中,可能存在以下情况:

  1. 根节点id不固定,可能是0null或其他
  2. 属性标识不一定是idpIdchildren这三种
  3. 转换树形结构的同时,可能会对当前子节点数据进行特殊处理

可将该动态变化的内容当做入参,封装一个统一的公共方法。

/**
 * 将数组转换为树形结构
 * @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)

image.png