开篇
首先声明,本文的产生完全是由于实际工作中遇到的问题做的解题,或是作者本人遇到的问题,或是本人身边的朋友、同事、同学在工作当中遇到的问题拿出来展开分析、实现,为的是帮助更多网络上会遇到同样问题的小伙伴们,让他们能够少走弯路快速提升自己的编程思路,当然如果小伙伴们有更好的解题思路也可以私信作者,大家互相学习共同成长
该问题来源于实际工作当中案例,我们有时候可能会遇到这样的情况,比如我们做前端项目时候用 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
组件会是这样的效果
然而这并不是我们要的效果,我们想要的效果是这样的
那么我们就来分析一下这种情况。
分析
数据特点
- 后端返回的数据是根据
id
字段确定数据的唯一性的 - 数据是通过
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>
更多文章引导
结尾
至此我们的功能就实现了,感谢您的观看,如果喜欢,请关注收藏,您的支持就是我的动力,后续我会出更多文章分享给大家