将后端传递的扁平化数据转成树形结构的方法指南

将扁平数据转换为树形结构的方法指南

在实际前端开发中,后端经常返回的是扁平化结构的列表数据,例如菜单组织架构文件目录等。为了在前端以树形结构进行渲染(如 Vant 的 TreeSelect、ElementUI 的 Tree),我们就需要将这类数据格式化为嵌套结构的树形数据。

本文将带你从思路剖析代码实现,并提供兼容性方案与完整演示,帮助你掌握数据结构转化的核心技巧。

一、问题背景

后端返回的原始数据如下所示,每一项通过 parentId 字段指示其所属的父节点:

[
  { "id": 48, "parentId": 47, "workspaceName": "工艺计划书合集", "workspaceCode": "A8-B1" },
  { "id": 47, "parentId": null, "workspaceName": "工艺计划书", "workspaceCode": "A8" },
  ...
]

这是一种扁平数据结构(Flat List),而我们需要的,是如下这种具有嵌套结构的树形结构(Tree):

[
  {
    "id": 47,
    "workspaceName": "工艺计划书",
    "children": [
      {
        "id": 48,
        "workspaceName": "工艺计划书合集"
      }
    ]
  },
  ...
]

二、思路分析:如何从列表构造出树?

数据转树的核心本质:找到每个节点的父节点,并将其挂载为子节点

步骤分为两轮遍历:

  1. 首先遍历整个数组,把每个节点放进一个 Map 中(通过 id 映射);

  2. 再次遍历数组,判断每个节点的 parentId 是否在 Map 中:

    • 是:将当前节点 push 到其父节点的 children 数组中;

    • 否:说明是根节点,加入结果树的根节点数组。

三、现代写法(ES6 Map 实现)

function listToTree(data) {
  const map = new Map();
  const tree = [];

  data.forEach(item => {
    item.children = [];
    map.set(item.id, item);
  });

  data.forEach(item => {
    if (item.parentId !== null && map.has(item.parentId)) {
      map.get(item.parentId).children.push(item);
    } else {
      tree.push(item);
    }
  });

  return tree;
}

✅ 优势:结构清晰,利用 Map 高效查找,适用于现代浏览器环境(IE 不支持)

四、兼容写法(若依框架风格)

如果项目需要兼容老旧浏览器(如 IE11),可以采用传统对象存储方式:

export function handleTree(data, id = 'id', parentId = 'parentId', children = 'children') {
  const childrenMap = {};
  const nodeMap = {};
  const tree = [];

  for (let item of data) {
    const pid = item[parentId];
    if (!childrenMap[pid]) childrenMap[pid] = [];
    childrenMap[pid].push(item);
    nodeMap[item[id]] = item;
  }

  for (let item of data) {
    const pid = item[parentId];
    if (!nodeMap[pid]) tree.push(item);
  }

  const adaptChildren = node => {
    if (childrenMap[node[id]]) {
      node[children] = childrenMap[node[id]];
      node[children].forEach(child => adaptChildren(child));
    }
  };

  tree.forEach(root => adaptChildren(root));
  return tree;
}

✅ 优势:兼容旧浏览器、适合低版本 Vue 项目(如 Vue2.x + Element)

五、两种方案对比总结

维度ES6 写法(Map)若依写法(传统对象)
性能快(O(n)查找)稍慢(对象查找)
代码简洁性更简洁更冗长
浏览器兼容性现代浏览器(Chrome/Edge)包括 IE 浏览器
场景适用Vue3/ViteVue2/老旧项目

六、完整运行示例(HTML + JS)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>树形结构转换示例</title>
  </head>
  <body></body>
  <script>
    const data = [
      { id: 48, parentId: 47, workspaceName: '工艺计划书合集', workspaceCode: 'A8-B1' },
      { id: 47, parentId: null, workspaceName: '工艺计划书', workspaceCode: 'A8' },
      { id: 26, parentId: null, workspaceName: '0501DKD工作指导书', workspaceCode: 'A2' },
      { id: 29, parentId: 26, workspaceName: '月度计划工艺包装方案A', workspaceCode: 'A2-B1' },
      { id: 35, parentId: 26, workspaceName: '月度计划工艺包装方案B', workspaceCode: 'A2-B2' },
    ];

    function listToTree(data) {
      const map = new Map();
      const tree = [];

      data.forEach((item) => {
        item.children = [];
        map.set(item.id, item);
      });

      data.forEach((item) => {
        if (item.parentId !== null && map.has(item.parentId)) {
          map.get(item.parentId).children.push(item);
        } else {
          tree.push(item);
        }
      });

      return tree;
    }

    console.log('Tree:', listToTree(data));
  </script>
</html>

最后,如果您觉得本文对你有帮助,欢迎 👍 点赞 / ⭐ 收藏 / 💬 评论!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值