JS数组转树形结构

188 阅读2分钟

前言

假设现在前端有个多级菜单需要渲染,但是菜单目录需要从服务器取得,但是服务器在数据库中存储的菜单数据是一张一维表格,表格中的每条数据通过pid来进行父级菜单的关联。

那么问题来了,前端渲染需要的是一个Tree的数据结构,但是我们从接口拿到的数据是一维数组的数据结构,要实现渲染,就需要转数据结构。

image.png

条件说明

  1. 每一条数据都有id和pid,id表示自身唯一的标记,pid则是指向父级id的地址;
  2. 最顶级的数据可能没有pid,但也有可能有pid,但是在数组中是唯一的,所以也应该作为最顶层的父级元素;
// 假设这是我们拿到的数据
const menuList = [
  {
    id: 11,
    pid: 2,
    name: 'list',
  },
  {
    id: 2,
    pid: 1,
    name: 'ListBase',
  },
  {
    id: 3,
    pid: 11,
    name: 'ListCard',
  },
  {
    id: 4,
    pid: 1,
    name: 'form',
  },
  {
    id: 5,
    pid: 2,
    name: 'FormBase',
  },
  {
    id: 6,
    pid: 11,
    name: 'FormStep',
  },
]
// 这是我们希望转换后的数据
[{
  "id": 2,
  "pid": 1,
  "name": "ListBase",
  "children": [{
    "id": 11,
    "pid": 2,
    "name": "list",
    "children": [{
      "id": 3,
      "pid": 11,
      "name": "ListCard"
    }, {
      "id": 6,
      "pid": 11,
      "name": "FormStep"
    }]
  }, {
    "id": 5,
    "pid": 2,
    "name": "FormBase"
  }]
}, {
  "id": 4,
  "pid": 1,
  "name": "form"
}]

方法一:递归

思路

  1. 原数组保持不变,生成一个新的tree数组,用来存储树形结构;
  2. 需要先在原数组的基础上,进行遍历,遍历的目标是,要找到列表中符合条件:pid在列表中唯一的全部数据;
  3. 将第二步的唯一值,放到我们的tree中,作为最顶层的数据;
  4. 然后对原列表进行遍历,在tree中找到满足条件的值(node.pid === treeNode.id),并对treeNode的children 进行插入操作;
  5. 对于找不到的项,在其children项中递归查询;

代码

function findNodeInTree(tree, node) {
  tree.forEach(treeNode => {
    if (treeNode.id === node.pid) {
      const children = treeNode?.children || []
      treeNode.children = [...children, node];
    } else {
      if (treeNode.children) {
        findNodeInTree(treeNode.children, node)
      }
    }
  })
}
function transform(list = []) {
  let tree = []
  const root = []
  list.forEach((sNode) => {
    const index = list.findIndex(node => sNode.pid === node.id)
    if (index === -1) {
      root.push(sNode)
    }
  })
  tree = [...root]
  list.forEach((node) => {
    findNodeInTree(tree, node)
  })
  return tree
}

const res = transform(menuList)

const fs = require('fs')
fs.writeFile('./tree.json', JSON.stringify(res), (error) => {
  error ? console.log(error) : console.log('success')
})

总结 核心就两个:找顶层数据;递归操作;

方法二:哈希表(散列表)

思路

  1. 新建一个map对象,用于存储对应的值;
  2. 遍历对象,判断条件,push内容,利用对象中的地址引用,快速实现树形结构的转换;

代码

function transform(list = []) {
  const map = {};
  list.forEach((node) => {
    if (!map[node.id]) {
      map[node.id] = { ...node, children: [] };
      debugger;
    } else {
      map[node.id] = { ...node, children: map[node.id].children };
      debugger;
    }
    if (node.pid && !map[node.pid]) {
      map[node.pid] = { id: node.pid, children: [] };
      debugger;
    }
  });
  const tree = [];
  Object.values(map).forEach((node) => {
    if (node.pid) {
      map[node.pid].children.push(node);
    } else {
      tree.push(node);
    }
  });
  return tree;
}

const res = transform(menuList)

const fs = require('fs')
fs.writeFile('./tree.json', JSON.stringify(res), (error) => {
  error ? console.log(error) : console.log('success')
})