使用 JavaScript 将二维数组转换为树形结构(附完整代码解析)

在实际开发中,我们经常会遇到将扁平数据(如数据库查询结果)转换为树形结构的需求,例如用于菜单栏、权限系统、组织架构等场景。本文将详细介绍如何使用 JavaScript 实现一个通用的二维数组转树形数组的方法,并提供完整的函数实现与示例。

问题描述

假设我们有一个扁平化的二维数组,其中每个元素都有一个唯一标识 id 和一个指向父节点的字段(如 parentId),如下所示:

const list = [
  { id: 1, name: '一级菜单', parentId: null },
  { id: 2, name: '二级菜单A', parentId: 1 },
  { id: 3, name: '二级菜单B', parentId: 1 },
  { id: 4, name: '三级菜单A-1', parentId: 2 },
  { id: 5, name: '三级菜单A-2', parentId: 2 }
]

我们的目标是将其转换为具有嵌套 children 字段的树形结构:

[
  {
    id: 1,
    name: '一级菜单',
    parentId: null,
    children: [
      {
        id: 2,
        name: '二级菜单A',
        parentId: 1,
        children: [
          { id: 4, name: '三级菜单A-1', parentId: 2, children: [] }
        ]
      },
      ...
    ]
  }
]

解决方案:arrToTree 函数详解

下面是实现该功能的核心函数:

/**
 * 将二维数组转为树形数组
 * @param arr 数组
 * @param idFieldName 节点唯一标识字段名,默认 'id'
 * @param parentFieldName 父节点标识字段名,默认 'parent'
 */
export function arrToTree(arr = [], idFieldName = 'id', parentFieldName = 'parent') {
	const map = {}
	const tree = []

	// 第一步:将所有节点放入 map 中,方便后续查找
	arr.forEach(item => {
		map[item[idFieldName]] = {
			...item,
			children: []
		}
	})

	// 第二步:遍历数据,将每个节点挂到对应的 parentFieldName 下
	arr.forEach(item => {
		const node = map[item[idFieldName]]
		if (item[parentFieldName] === null) {
			// 根节点
			tree.push(node)
		} else {
			// 找到父节点
			const parent = map[item[parentFieldName]]
			if (parent) {
				parent.children.push(node)
			}
		}
	})

	return tree
}

函数说明

  1. 构建映射表 map
    首次遍历数组时,我们将每个元素存入一个以 id 为键的对象中,便于后续快速查找父节点。
  2. 二次遍历构建树结构
    第二次遍历时,根据当前节点的 parentId 查找其父节点,并将当前节点加入父节点的 children 数组中。如果是根节点(即 parentIdnull),则直接添加到结果数组 tree 中。

示例用法

const data = [
  { id: 1, name: '一级菜单', parentId: null },
  { id: 2, name: '二级菜单A', parentId: 1 },
  { id: 3, name: '二级菜单B', parentId: 1 },
  { id: 4, name: '三级菜单A-1', parentId: 2 },
]

const tree = arrToTree(data, 'id', 'parentId')
console.log(JSON.stringify(tree, null, 2))

输出结果如下:

[
  {
    "id": 1,
    "name": "一级菜单",
    "parentId": null,
    "children": [
      {
        "id": 2,
        "name": "二级菜单A",
        "parentId": 1,
        "children": [
          {
            "id": 4,
            "name": "三级菜单A-1",
            "parentId": 2,
            "children": []
          }
        ]
      },
      {
        "id": 3,
        "name": "二级菜单B",
        "parentId": 1,
        "children": []
      }
    ]
  }
]
+1

发表回复