本文介绍如何实现一个基于 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>
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>
|
注: 如果你使用了模块化构建系统,像包含了 webpack
或 Browserify
的 @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>
|
至此就实现了最基本的树结构功能。
仓库地址
仓库地址