本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
前几天面试题中遇到一个题是将普通数据转为树形数据,我觉得考察这个知识点还是很有用的,毕竟在工作中我们经常会根据后端返回的数据进行处理,其中转化树形数据尤为常见,所以对该知识点再进行复习整理下。
1.使用原生方式转为树形数据并渲染到页面
源码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>普通数据转为树形数据</title>
<style type="text/css">
*{
margin: 0;
}
.box{
box-sizing: border-box;
width: 500px;
height: 100%;
margin: 10px auto;
padding: 20px;
border: 1px solid hotpink;
}
li{
margin-bottom: 10px;
font-size: 13px;
}
</style>
</head>
<body>
<div class="box"></div>
<script type="text/javascript">
let list = [
{ id: 21, title: '海南省', pid: 0 },
{ id: 268, title: '海口市', pid: 21 },
{ id: 269, title: '三亚市', pid: 21 },
{ id: 270, title: '三沙市', pid: 21 },
{ id: 271, title: '儋州市', pid: 21 },
{ id: 22, title: '重庆市', pid: 0 },
{ id: 23, title: '四川省', pid: 0 },
{ id: 288, title: '成都市', pid: 23 },
{ id: 289, title: '自贡市', pid: 23 },
{ id: 290, title: '攀枝花市', pid: 23 },
{ id: 291, title: '泸州市', pid: 23 },
{ id: 292, title: '德阳市', pid: 23 },
{ id: 293, title: '绵阳市', pid: 23 },
{ id: 2436, title: '锦江区', pid: 288 },
{ id: 2437, title: '青羊区', pid: 288 },
{ id: 2438, title: '金牛区', pid: 288 },
{ id: 2439, title: '武侯区', pid: 288 },
{ id: 2440, title: '成华区', pid: 288 },
]
// 定义一个空字符串用来存渲染的字符串
let menus = '';
function getTreeData(id, array){
let childArray = getParent(id, array);
if(childArray.length > 0){
menus += `<ul>`;
for(let i in childArray){
menus += '<li>' + childArray[i].title;
getTreeData(childArray[i].id, array);
menus += '</li>';
}
menus += '</ul>';
}
}
// 定义获取父级菜单的方法(一级为二级的父级, 二级为三级的父级)
function getParent(id, array){
let arr = [];
for(let i in array){
if(array[i].pid === id){
arr.push(array[i])
}
}
return arr;
}
let oBox = document.getElementsByTagName('div')[0];
getTreeData(0, list)
oBox.innerHTML = menus
</script>
</body>
</html>
渲染结果
主要是利用递归的方式来实现树形结构转换
2.结合 ui 组件实现
这里使用的是 iview 中的 Tree 组件, App.vue 代码如下:
<template>
<div id="app">
<h3>树形数据展示</h3>
<Tree :data="treeData"></Tree>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
list: [
{ id: 21, title: '海南省', pid: 0 },
{ id: 268, title: '海口市', pid: 21 },
{ id: 269, title: '三亚市', pid: 21 },
{ id: 270, title: '三沙市', pid: 21 },
{ id: 271, title: '儋州市', pid: 21 },
{ id: 22, title: '重庆市', pid: 0 },
{ id: 23, title: '四川省', pid: 0 },
{ id: 288, title: '成都市', pid: 23 },
{ id: 289, title: '自贡市', pid: 23 },
{ id: 290, title: '攀枝花市', pid: 23 },
{ id: 291, title: '泸州市', pid: 23 },
{ id: 292, title: '德阳市', pid: 23 },
{ id: 293, title: '绵阳市', pid: 23 },
{ id: 2436, title: '锦江区', pid: 288 },
{ id: 2437, title: '青羊区', pid: 288 },
{ id: 2438, title: '金牛区', pid: 288 },
{ id: 2439, title: '武侯区', pid: 288 },
{ id: 2440, title: '成华区', pid: 288 },
],
}
},
computed: {
// 方法1: 直接遍历list数组,给每项添加children,通过筛选 pid 和 id值是否相等来添加值
treeData() {
let newList = JSON.parse(JSON.stringify(this.list))
let result = newList.map(item=>{
item.children = newList.filter(val=>val.pid === item.id)
return item
})
// 最后筛选出 pid 为 0 的项
return result.filter(item=>item.pid === 0)
}
/*
方法2:遍历数组,使用 item.id 和 item作为对象的键值对进行标记,添加children属性,判断当前项的 pid 是否为0,为0就添加到 result 数组中,不为0就添加到 item.pid 对应对象的 children中
*/
// treeData() {
// let newList = JSON.parse(JSON.stringify(this.list))
// let result = [];
// let obj = {};
// for (let item of newList) {
// obj[item.id] = item;
// obj[item.id].children = [];
// if (item.pid === 0) {
// result.push(obj[item.id])
// } else {
// obj[item.pid].children.push(item)
// }
// }
// return result
// }
/* 方法3: 使用递归获取子集 */
// treeData(){
// let newList = JSON.parse(JSON.stringify(this.list))
// return this.toTreeData(newList, 0)
// }
},
methods:{
toTreeData(arr, pid){
let result = [];
this.getChildren(arr, result, pid)
return result
},
getChildren(data, result, pid){
for(let item of data){
if(item.pid === pid){
let newItem = {...item, children:[]}
result.push(newItem)
this.getChildren(data, newItem.children, item.id)
}
}
}
}
}
</script>
<style>
#app {
width: 500px;
height: 100%;
border: 1px solid red;
margin: 10px auto;
}
#app h3 {
width: 100%;
text-align: center;
}
</style>
有个小缺陷是筛选后的数据作为 Tree 组件的值,页面渲染出来的数据默认是没有展开的,需要设置它的 expand 属性并通过判断当前项的 children 是否有值来添加