原标题:Vue 递归组件构建一个树形菜单
Vue.js 中的递归组件是一个可以调用自己的组件例如:
Vue.component('recursive-component', {
template: ``
});
递归组件一般用于博客上显示评论,树形菜单或者嵌套菜单。
一、初始化
我们直接使用 vue 提供的脚手架 vue-cli 来初始化我们的工程:
# 搭建项目
vue init webpack-simple tree-menu
# 进入项目
cd tree-menu
# 依赖安装
npm install
# 运行项目
npm run dev
现在我们的环境已经准备好了,在初始化的项目中,有一些不需要的代码我们可以自己动手删除。
二、数据结构
在本教程中,我们将使用一个树结构的数据来作为我们的每个菜单项,其中每个节点都是一个对象:
label 属性,作为菜单的列表,如果该菜单下还有子菜单,就需要一个 nodes 属性,这是一个或多个节点的数组。
这里需要注意的是像所有的树结构一样,必须有一个根节点,然后从这个根节点进行无限嵌套。
let tree = {
label: 'root',
nodes: [
{
label: 'item1',
nodes: [
{
label: 'item1.1'
},
{
label: 'item1.2',
nodes: [
{
label: 'item1.2.1'
}
]
}
]
},
{
label: 'item2'
}
]
}
三、递归组件
将 src 下的 app.vue 修改为 TreeMenu.vue ,该组件将作为我们的树形的递归组件,用来显示当前节点和自己的子节点。
v-for="node in nodes"
:nodes="node.nodes"
:label="node.label"
>
<>
export default {
props: [ 'label', 'nodes' ],
name: 'tree-menu'
}
>
如果您正在使用递归组件,则必须使用全局注册 Vue.component,或者给它一个 name 属性。否则,组件的任何子节点将无法解析进一步的调用,并且会得到未定义的组件错误。
和任何递归函数一样,我们需要一个条件来结束递归,否则渲染将无限期地继续下去,最终会导致堆栈溢出。
在我们的树形菜单中,当我们到达一个没有子节点的节点时,我们想要停止递归。你可以用 v-for ,如果 nodes 数组未定义,tree-menu 则不会继续调用组件。
...
四、主模块
在主模块 main.js 中声明一个树形结构的数据,并将该数据作为 data 属性的值,并引入 TreeMenu 组件 。
/* ./src/main.js */
import TreeMenu from './TreeMenu.vue'
let tree = {
...
}
new Vue({
el: '#app',
data: {
tree
},
components: {
TreeMenu
}
})
由于我们的数据结构只有一个单一的根节点。为了开始递归,我们将 TreeMenu 组件作为根节点的子节点,修改根目录下的 index.html 文件:
五、缩进
为了让用户可以直观地识别子组件,就有必要对每一层的子节点进行缩进。为了实现该效果,我们通过添加一个 depth 来实现。使用这个值来动态设置 css 中的 transform 属性,从而实现缩进效果。
v-for="node in nodes"
:nodes="node.nodes"
:label="node.label"
:depth="depth + 1"
>
<>
export default {
props: [ 'label', 'nodes', 'depth' ],
name: 'tree-menu',
computed: {
indent() {
return { transform: `translate(${this.depth * 50}px)` }
}
}
}
>
同时在 index.html 上将 depth 设置为从零,也就是初始的时候是没有缩进的。这样每次将该值传递给任何子节点时,该值都会增加。
:label="tree.label"
:nodes="tree.nodes"
:depth="0"
>
注意:depth 的值要确保它是一个 Java 数字而不是一个字符串。
六、收缩
在初始的时候,应该只显示根节点,其他节点全部隐藏,然后通过鼠标点击的时候,能够展开各个节点。
为此,我们将添加一个本地状态 showChildren 。如果该值为 false,则不显示子节点,如果为 true 则显示子节点。而这个值应该通过鼠标点击节点来进行切换,所以我们需要一个点击事件:
v-if="showChildren"
v-for="node in nodes"
:nodes="node.nodes"
:label="node.label"
:depth="depth + 1"
>
<>
export default {
props: [ 'label', 'nodes', 'depth' ],
data() {
return { showChildren: false }
},
name: 'tree-menu',
computed: {
indent() {
return { transform: `translate(${this.depth * 50}px)` }
}
},
methods: {
toggleChildren() {
this.showChildren = !this.showChildren;
}
}
}
>
七、样式
经过以上步骤就已经完成了一个基本的树形菜单。为了使 UI 效果更佳,我们可以添加一个加号和减号图标,使用户界面更加明显。以下是全部的代码:
:label="tree.label"
:nodes="tree.nodes"
:depth="0"
>
/* ./src/main.js */
import Vue from 'vue'
import TreeMenu from './TreeMenu.vue'
import './assets/app.css'
//定义树形菜单数据
let tree = {
label: 'root',
nodes: [
{
label: 'item1',
nodes: [
{
label: 'item1.1'
},
{
label: 'item1.2',
nodes: [
{
label: 'item1.2.1'
}
]
}
]
},
{
label: 'item2'
}
]
}
new Vue({
el: '#app',
// render: h => h(App)
data: {
tree
},
components: {
TreeMenu
}
})
{{ label }}
v-if="showChildren"
v-for="node in nodes"
:nodes="node.nodes"
:label="node.label"
:depth="depth + 1"
>
<>
export default {
props: [ 'label', 'nodes', 'depth' ],
name: 'tree-menu',
data() {
return { showChildren: false }
},
computed: {
iconClasses: function iconClasses() {
return {
'plus': !this.showChildren,
'minus': this.showChildren
};
},
labelClasses: function labelClasses() {
return { 'has-children': this.nodes };
},
indent() {
return { transform: `translate(${this.depth * 50}px)` }
}
},
methods: {
toggleChildren() {
this.showChildren = !this.showChildren;
}
}
}
>
./src/assets/app.css
body {
font-family: sans-serif;
font-size: 18px;
font-weight: 300;
line-height: 1em;
}
.container {
width: 300px;
margin: 0 auto;
}
.tree-menu .label-wrapper {
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid #ccc;
}
.tree-menu .label-wrapper .has-children {
cursor: pointer;
}
.plus:before {
content:'+';
}
.minus:before {
content:'-';
}
本文章由源码时代H5学科讲师原创!
转载须注明出处(http://www.itsource.cn)!感谢大家的配合!返回搜狐,查看更多
责任编辑: