递归组件实现树结构

本文介绍如何实现一个基于 vue 的递归组件的树结构!

功能

  • 根据树形数据渲染出树形结构
  • 点击某个节点能够收缩其子节点
  • 等其他功能…

实现思路

组件在其内部是可以调用其组件自身的,使用递归的思想循环调用树列表组件自身,就可以循环往复的渲染出所有树节点,但是必须确定跳出递归的条件,避免循环引用导致无限循环而出现如下错误。

1
Uncaught RangeError: Maximum call stack size exceeded

根据树形数据结构,我们可以将跳出循环的条件设置为 是否存在子节点数据 ,如果有子节点数据,那继续循环,否则,跳出循环,只渲染当前节点。

代码

测试代码环境为 vue-cli-3,组件使用单文件组件。

treeList.vue 组件

该组件为渲染树本身

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

<template>
<ul>
<li v-for="(child,index) in data" :key="index">
<treeLabel v-if="child.children" :folder="child"></treeLabel>
<span v-else>{{child.label}}</span>
</li>
</ul>
</template>

<script>

import treeLabel from './treeLabel.vue'

export default {
name: "treeList",
components: {
treeLabel
},
props: ['data']
}
</script>


<style scoped>
ul { list-style: none;}
p { margin: 0}
</style>

treeLabel.vue 组件

该组件功能为,渲染当前节点标题,及其子组件数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

<template>
<p>
<span @click="toggle">{{folder.label}}</span>
<treeList v-show="isshow" :data="folder.children"></treeList>
</p>
</template>


<script>
// 异步导入 treeList 组件,告诉模块系统 -> 我们不需要先解析 treeList 组件,等到用到的时候再解析加载
const treeList = () => import('./treeList.vue')

export default {
name: "treeLabel",
data(){
return {
isshow: false
}
},
props: ['folder'],
components: {
treeList
},
methods: {
toggle(){
this.isshow = !this.isshow;
}
}
}
</script>

<style scoped>
span { cursor: pointer;}
</style>


注: 如果你使用了模块化构建系统,像包含了 webpackBrowserify@vue/cli,你会遇到像下面这样的错误:

1
Failed to mount component: template or render function not defined.

为了解释这里发生了什么,我们先把两个组件称为 A 和 B。模块系统发现它需要 A,但是首先 A 依赖 B,但是 B 又依赖 A,但是 A 又依赖 B,如此往复。这变成了一个循环,不知道如何不经过其中一个组件而完全解析出另一个组件。

为了解决这个问题,我们需要给模块系统一个点,在那里 A 反正是需要 B 的,但是我们不需要先解析 B

所以,对于上面我们定义的两个组件,我们可以把 treeLabel 组件作为那个点,而为了告诉 模块系统 我们不需要先解析 treeList 组件,而是等到用到的时候再加载解析她。那么,我们可以这样做:

1
const treeList = () => import('./treeList.vue')

没错!在 webpack 这样的模块系统中,我们可以使用异步的 import() 来注册异步组件,这样只有在 treeList 组件被用到的时候才会被模块系统解析。

对于以上问题,只是会出现在 局部注册组件 时出现,如果你是以 全局注册组件 的显示定义有循环引用的两个组件时就不会出现这样的情况了。

1
2
3
4
5
6
Vue.component('treeLabel', {
// ...
})
Vue.component('treeList', {
// ...
})

使用

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<template>
<div id="app">
<treeList :data="list" />
</div>
</template>

<script>
import treeList from './components/treeList.vue'

export default {
name: "app",
components: {
treeList
},
data(){
return {
list: [
{
label: "一级菜单1",
children: [
{
label: "二级菜单1",
children: [
{
label: "三级菜单1"
}
]
},
{
label: "二级菜单2"
}
]
},
{
label: "一级菜单2"
},
{
label: "一级菜单3",
children: [
{
label: "二级菜单1"
}
]
}
]
}
}
};
</script>


至此就实现了最基本的树结构功能。

仓库地址

仓库地址