全局组件实现递归树,避免循环引用
概述
目录分类展示会通常要用到树形结构。本例结合vue的父子组件,采用递归渲染,实现一个基于树的curd小demo
知识点
- 父子组件递归渲染
class
样式对象写法,CSS伪元素::before
用法vue
指令v-model
,内联模板x-template
- 事件
dblclick blur
, 防止事件传播修饰符stop
- 引用父实例属性
$parent
, 全局方法Vue.set Vue.delete
,子组件类型校验props
效果图
节点模型
树形结构,节点通常由数据域+子节点引用组成。缺少指向子节点引用的节点,称之为叶子节点。
同理,缺少父节点的节点谓之根节点。二者分别代表递归的出口和入口,在逻辑部分通过动态响应实现渲染效果。
- 下面这段代码描述了一个
data
基本treedata
模型节点对象。name
为string
类型,children
是数组类型。
var data = {
name: 'home',
children: [
{ name: 'hello.js' },
]
}
模板
- 以
x-template
形式描述当单个节点,然后递归复用该组件。 - 用文件与文件夹关系类比,文件则显示文件名,有文件夹则迭代。
- 所呈现的无非文件或文件夹,遇文件夹则递归使用组件本身。
用v-model实现双向绑定,双击文件名则达到文件变身文件夹,此外有相应的清除,删除,修改vue事件绑定
<script type="text/x-template" id="item-template">
<li>
<div :class="{foldero:isFolder && open ,folderc: !(isFolder && open),file:!isFolder}"
@click="toggle" @dblclick="changeType">
<span v-show="show">{{ name }}</span>
<input type="text" v-show="!show" v-model="name"
@blur="doneEdit(name)"
@keyup.enter="doneEdit(name)"
@keyup.esc="cancelEdit()"
@click.stop @dblclick.stop >
<span @click.stop='editToggle'>[e]</span>
<span @click.stop='delChild' v-show="isFolder">[c]</span>
<span @click.stop='delFile' v-show="!isFolder">[x]</span>
<span v-if="isFolder">[{{ open ? '-':'+'}}]</span>
</div>
<ul v-show="open" v-if="isFolder">
<item class="item"
v-for="(treeData,index) in this.model.children" :
model="treeData" :key="index"/>
<li><span class="add" @click.stop="addChild">[append] </span></li>
</ul>
</li>
</script>
组件
- 注册全局组件
item
- 需要注意在直接在
DOM
(即非字符串的模板如x-template
) 中使用时只有item
有效 - 约定校验数据类型是
Object
,属性名model
视为组件接收父组件的模型数据接口
- 需要注意在直接在
Vue.component('item', {
template: '#item-template',
// 类型校验,接收父组件传递过来的数据(只读)
props: {
model: Object,
},
computed: {
isFolder() {
return this.model.children && this.model.children.length
}
},
...
方法
- 动态增/删新节点
Vue.delete
清除文件夹下子节点Vue.set
向响应式对象中添加一个新属性,需确保这个新属性同样是响应式的,以触发视图更新
methods: {
changeType() {
if (!this.isFolder) {
Vue.set(this.model, 'children', [])
this.addChild()
this.open = true
}
},
addChild() {
this.model.children.push({name: 'new file'})
},
delChild() {
if (this.isFolder) {
Vue.delete(this.model, 'children')
}
}
...
- 删除文件
- 通过
$prarent
引用父实例节点删除当前节点 - 用引用类型数组方法,
indexOf
定位当前模型在父节点中的索引,splice
改变数据源 - 需要排除没有父节点(即当前节点是根节点情况),以及判断父节点是一个完整节点(是否存在有效子节点)
- 通过
delFile() {
if (this.$parent.model === undefined) {
return
}
var parent = this.$parent.model.children
if (parent && Array.isArray(parent) && parent.length) {
parent.splice(parent.indexOf(this.model), 1)
}
},
源码
本作品采用《CC 协议》,转载必须注明作者和本文链接