精读 Vue 官方文档系列 🎉
什么是递归组件?
与普通的递归的含义相同。组件递归就是组件调用自己,Vue 中需要在模板中使用组件的 name 来实现组件递归。
个人总结
当我们递归模板时,比如生成一个树形结构,一个好的设计,应该让树形结构中的每一个节点都要在递归中创建一个唯一的组件实例与之对应,好处就是每个节点的状态都可以独立维护。反之,如果我们递归的组件没有去区分内容是否需要递归,而是一股脑的都包含在一个组件中,那么就会导致多个节点存在在一个组件实例中,不利于后期的拓展。
在递归中为每一个节点生成一个独立组件,这样节点的状态可以使用计算属性进行缓存。
具体示例
现在,我们来设计递归组件的结构,我们会创建两个组件:
root:递归组件的根组件,对外暴露递归组件实例。folder:递归组件的节点组件,它用于渲染为一个文件/文件夹。
目录结构如下:
── Tree
│ ├── mixins.js #逻辑复用
│ ├── Tree.vue #递归组件的 root 组件
│ ├── TreeFolder.vue #树形结构中的每个节点组件
mixins.js
import Vue from "vue";
const getRandomStr = () => Math.random().toString(16).slice(-10);
export default {
methods: {
addItem(item) {
item.children.push({ name: getRandomStr() })
},
addRootItem(item) {
item.push({ name: getRandomStr() })
},
makeFolder(item) {
if (!item.children) {
Vue.set(item, 'children', []);
this.addItem(item);
}
}
}
}
Tree.vue
<template>
<ul class="tree">
<tree-folder
v-for="item in treeData"
:key="item.name"
:tree-item="item"
/>
<li class="add" @click="addRootItem(treeData)">+</li>
</ul>
</template>
<script>
import TreeFolder from "./TreeFolder.vue";
import TreeMixins from "./mixins";
export default {
name: "Tree",
mixins: [TreeMixins],
components: {
TreeFolder,
},
props: {
treeData: {
type: Array,
default: () => [],
},
},
};
</script>
<style scoped>
.tree {
margin: 0;
width: 320px;
text-align: left;
}
</style>
TreeFolder.vue
<template>
<li @dblclick="makeFolder(treeItem)">
<div class="tree-folder">
<span> {{ treeItem.name }}</span>
<a v-show="isFolder" @click="toggle">[{{ isOpen ? "-" : "+" }}]</a>
</div>
<ul v-if="isFolder" class="tree-list" v-show="isOpen">
<tree-folder
v-for="item in treeItem.children"
:key="item.name"
:tree-item="item"
@add-item="addItem"
/>
<li class="add" @click="addItem(treeItem)">+</li>
</ul>
</li>
</template>
<script>
import TreeMixins from "./mixins";
export default {
name: "TreeItem",
mixins: [TreeMixins],
props: {
treeItem: Object,
},
data() {
return {
isOpen: false,
};
},
computed: {
isFolder() {
return this.treeItem.children && this.treeItem.children.length;
},
},
methods: {
toggle() {
this.isOpen = !this.isOpen;
},
},
};
</script>
<style scoped>
.tree-list {
margin: 0;
width: 320px;
text-align: left;
}
</style>
现在,通过这样的划分,递归组件的根由一个单独的 Tree 组件进行管理。而每个子节点都会对应一个唯一的 TreeFolder 组件。