javascript 将数据列表处理成树形有层级关系的列表方法(思路+代码)

本文介绍了如何将后端返回的一维数据列表转化为前端组件所需的树形结构,特别是针对Vue+ElementUI场景。通过分析数据特点和确定上下级关系,提出添加'children'属性并利用Map数据结构的方法实现转换。提供了基础实现和优化提升的代码示例,以及在实际组件中的应用。
摘要由CSDN通过智能技术生成

开篇

首先声明,本文的产生完全是由于实际工作中遇到的问题做的解题,或是作者本人遇到的问题,或是本人身边的朋友、同事、同学在工作当中遇到的问题拿出来展开分析、实现,为的是帮助更多网络上会遇到同样问题的小伙伴们,让他们能够少走弯路快速提升自己的编程思路,当然如果小伙伴们有更好的解题思路也可以私信作者,大家互相学习共同成长

该问题来源于实际工作当中案例,我们有时候可能会遇到这样的情况,比如我们做前端项目时候用 vue+elementui 来写页面,用到了 el-tree 组件,该组件需要用到的数据结构是这样的

// 基础的树形结构展示
let list = [
    {
        label: '一级 1',
        children: [{
            label: '二级 1-1',
            children: [{
                label: '三级 1-1-1'
            }]
        }]
    }, {
        label: '一级 2',
        children: [{
            label: '二级 2-1',
            children: [{
                label: '三级 2-1-1'
            }]
        }, {
            label: '二级 2-2',
            children: [{
                label: '三级 2-2-1'
            }]
        }]
    }, {
        label: '一级 3',
        children: [{
            label: '二级 3-1',
            children: [{
                label: '三级 3-1-1'
            }]
        }, {
            label: '二级 3-2',
            children: [{
                label: '三级 3-2-1'
            }]
        }]
    }
]

然而后端返回的数据结构是这样的

// 后端返回的 response 数据
let list = [
    {
        label: '一级 1',
        id: 1,
        parentId: 0
    },
    {
        label: '一级 2',
        id: 2,
        parentId: 0
    },
    {
        label: '一级 3',
        id: 3,
        parentId: 0
    },
    {
        label: '二级 1-1',
        id: 4,
        parentId: 1
    },
    {
        label: '二级 2-1',
        id: 5,
        parentId: 2
    },
    {
        label: '二级 3-1',
        id: 6,
        parentId: 3
    },
    {
        label: '二级 3-2',
        id: 7,
        parentId: 3
    },
    {
        label: '三级 1-1-1',
        id: 8,
        parentId: 4
    },
    {
        label: '三级 2-1-1',
        id: 9,
        parentId: 5
    },
    {
        label: '三级 3-1-1',
        id: 10,
        parentId: 6
    },
    {
        label: '三级 3-2-1',
        id: 11,
        parentId: 7
    }
]

那么问题来了,怎么才能把后端返回的数据转换成我们想要的数据格式呢?这个格式看起来很简单,但是却难倒了很多小伙伴,所以在这里把这个问题拿出来跟大家分享一下我的实现思路;

很明显如果直接把后端返回的数据直接传给 el-tree 组件会是这样的效果

在这里插入图片描述

然而这并不是我们要的效果,我们想要的效果是这样的

在这里插入图片描述

那么我们就来分析一下这种情况。

分析

数据特点

  1. 后端返回的数据是根据 id 字段确定数据的唯一性的
  2. 数据是通过 parentId 字段确定上下级关系

解决思路

我的想法是这样的

我们可以考虑通过给每条数据添加 children 属性并定义为空数组的方式来实现数据改造

首先我们需要两个数组,一个是原数据,一个是以 id 为key值的map数据,然后遍历整个数据列表,在数据列表中定义一个父级数据变量,数据就取当前遍历项 parentId 的数据,如果存在则说明当前这条数据存在下级,如果不存在则说明当前遍历的数据没有下级数据,可以直接 push 给返回数据包

代码

代码实现(考虑基本实现)

    /**
     * 数组转树形结构数据
     * @param list  传入源数据列表
     * @param id  定义源数据中确定数据唯一性的属性
     * @param parentId  定义源数据中确定上下层级关系的属性
     */
    function arrToTree(list, id = 'id', parentId = 'parentId') {
        // 定义一个最终返回的数据包
        let result = []

        // 将数据处理成以唯一值ID为key的map结构数据
        let map = {}
        list.forEach(item => {
            map[item[id]] = item
        })
        // 根据子级数据是否有归属来确定应该挂载的节点数据
        list.forEach(item => {
            let parent = map[item[parentId]]
            if (parent) {
                if(!parent.children){
                    parent.children = []
                }
                parent.children.push(item)
            } else {
                result.push(item)
            }
        })
        // 返回处理后的数据
        return result
    }

代码优化提升(更多细节考虑)

    /**
     * 数组转树形结构数据
     * @param list  传入源数据列表
     * @param id  定义源数据中确定数据唯一性的属性
     * @param parentId  定义源数据中确定上下层级关系的属性
     */
    function arrToTree(list, id = 'id', parentId = 'parentId') {
        // 定义一个最终返回的数据包
        let result = []
        // 如果传进来的数据不是数组则返回空数组
        if (!Array.isArray(list)) {
            return result
        }
        // 为了方法能够通用首先将数据做一次净化处理
        list.forEach(item => {
            item.children && delete item.children
        })
        // 将数据处理成以唯一值ID为key的map结构数据
        let map = {}
        list.forEach(item => {
            map[item[id]] = item
        })
        // 根据子级数据是否有归属来确定应该挂载的节点数据
        list.forEach(item => {
            let parent = map[item[parentId]]
            if (parent) {
                // 利用或运算符来简化判断条件
                (parent.children || (parent.children = [])).push(item)
            } else {
                result.push(item)
            }
        })
        // 返回处理后的数据
        return result
    }

具体组件中的应用(基本应用)

组件 test.vue

<template>
  <el-tree :data="list" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>

<script>
export default {
  data() {
    return {
      list: [],
      data: [
        {
          label: '一级 1',
          id: 1,
          parentId: 0
        },
        {
          label: '一级 2',
          id: 2,
          parentId: 0
        },
        {
          label: '一级 3',
          id: 3,
          parentId: 0
        },
        {
          label: '二级 1-1',
          id: 4,
          parentId: 1
        },
        {
          label: '二级 2-1',
          id: 5,
          parentId: 2
        },
        {
          label: '二级 3-1',
          id: 6,
          parentId: 3
        },
        {
          label: '二级 3-2',
          id: 7,
          parentId: 3
        },
        {
          label: '三级 1-1-1',
          id: 8,
          parentId: 4
        },
        {
          label: '三级 2-1-1',
          id: 9,
          parentId: 5
        },
        {
          label: '三级 3-1-1',
          id: 10,
          parentId: 6
        },
        {
          label: '三级 3-2-1',
          id: 11,
          parentId: 7
        }],
      defaultProps: {
        children: 'children',
        label: 'label'
      }
    };
  },
  created() {
    this.list = this.arrToTree(this.data)
  },
  methods: {
    // 处理点击事件
    handleNodeClick(data) {
      console.log(data);
    },

    /**
     * 数组转树形结构数据
     * @param list  传入源数据列表
     * @param id  定义源数据中确定数据唯一性的属性
     * @param parentId  定义源数据中确定上下层级关系的属性
     */
    arrToTree(list, id = 'id', parentId = 'parentId') {
      // 定义一个最终返回的数据包
      let result = []
      // 如果传进来的数据不是数组则返回空数组
      if (!Array.isArray(list)) {
        return result
      }
      // 为了方法能够通用首先将数据做一次净化处理
      list.forEach(item => {
        item.children && delete item.children
      })
      // 将数据处理成以唯一值ID为key的map结构数据
      let map = {}
      list.forEach(item => {
        map[item[id]] = item
      })
      // 根据子级数据是否有归属来确定应该挂载的节点数据
      list.forEach(item => {
        let parent = map[item[parentId]]
        if (parent) {
          // 利用或运算符来简化判断条件
          (parent.children || (parent.children = [])).push(item)
        } else {
          result.push(item)
        }
      })
      // 返回处理后的数据
      return result
    }
  }
};
</script>

具体组件中的应用(高级应用)

封装的统一工具文件 /src/utils/index.js

...
    /**
     * 数组转树形结构数据
     * @param list  传入源数据列表
     * @param id  定义源数据中确定数据唯一性的属性
     * @param parentId  定义源数据中确定上下层级关系的属性
     */
    export const arrToTree = (list, id = 'id', parentId = 'parentId') => {
      // 定义一个最终返回的数据包
      let result = []
      // 如果传进来的数据不是数组则返回空数组
      if (!Array.isArray(list)) {
        return result
      }
      // 为了方法能够通用首先将数据做一次净化处理
      list.forEach(item => {
        item.children && delete item.children
      })
      // 将数据处理成以唯一值ID为key的map结构数据
      let map = {}
      list.forEach(item => {
        map[item[id]] = item
      })
      // 根据子级数据是否有归属来确定应该挂载的节点数据
      list.forEach(item => {
        let parent = map[item[parentId]]
        if (parent) {
          // 利用或运算符来简化判断条件
          (parent.children || (parent.children = [])).push(item)
        } else {
          result.push(item)
        }
      })
      // 返回处理后的数据
      return result
    }
...

组件 test.vue

<template>
  <el-tree :data="list" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>

<script>
import { arrToTree } from '@src/utils/index.js'
export default {
  data() {
    return {
      list: [],
      data: [
        {
          label: '一级 1',
          id: 1,
          parentId: 0
        },
        {
          label: '一级 2',
          id: 2,
          parentId: 0
        },
        {
          label: '一级 3',
          id: 3,
          parentId: 0
        },
        {
          label: '二级 1-1',
          id: 4,
          parentId: 1
        },
        {
          label: '二级 2-1',
          id: 5,
          parentId: 2
        },
        {
          label: '二级 3-1',
          id: 6,
          parentId: 3
        },
        {
          label: '二级 3-2',
          id: 7,
          parentId: 3
        },
        {
          label: '三级 1-1-1',
          id: 8,
          parentId: 4
        },
        {
          label: '三级 2-1-1',
          id: 9,
          parentId: 5
        },
        {
          label: '三级 3-1-1',
          id: 10,
          parentId: 6
        },
        {
          label: '三级 3-2-1',
          id: 11,
          parentId: 7
        }],
      defaultProps: {
        children: 'children',
        label: 'label'
      }
    };
  },
  created() {
    this.list = arrToTree(this.data)
  },
  methods: {
    // 处理点击事件
    handleNodeClick(data) {
      console.log(data);
    }
  }
};
</script>

更多文章引导

git非常实用的命令-删除无效的远程追踪分支

巧妙的解决web前端数字计算精度丢失的问题

vue实现用canvas写的手写签名组件

使用javascript计算两个日期之间的天数

javascript分别获取两个数组的差集、并集、交集

更完善的多功能精度计算的javascript代码

vue关于router路由的详细使用

git非常实用的命令-删除无效的远程追踪分支

使用canvas实现签字板的功能

使用VMware从零开始安装CentOS7.6系统

结尾

至此我们的功能就实现了,感谢您的观看,如果喜欢,请关注收藏,您的支持就是我的动力,后续我会出更多文章分享给大家

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐的逗号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值