什么是递归组件
- 如果某个组件通过组件名称引用它自己,这种情况就是递归组件;
- 实际开发中类似 Tree、Menu 这类组件,它们的节点往往包含子节点,子节点结构和父节点往往是相同的,这类组件的数据往往也是树形结构,这种都是使用递归组件的典型场景;
- 使用递归组件时,由于不能在组件内部导入它自己,所以设置组件 name 属性,用来查找组件定义,如果使用 SFC 则可以通过 SFC 文件名推断,组件内部通常也要有递归结束条件,比如 model.children 这样的判断;
- 查看生成渲染函数可知,递归组件查找时会传递一个布尔值给 resolveComponent,这样实际获取的组件就是当前组件本身;
为什么使用递归组件
对于一些有规律的 DOM 结构,如果再一遍遍的编写同样的代码,显然代码是比较繁琐和不科学的,而且自己的工作量会大大增加,那么有没有一种方法来解决这个问题呢?
答案是肯定的,可以通过 「递归」 方式来生成这个结构,当然在 Vue 模板中也是可以实现的,可以在 Vue 的组件中调用自己本身,这样就能达到目的;
在 Vue 中,组件可以递归的调用本身,但是有一些条件
该组件一定要有 name 属性;
要确保递归的调用有终止条件,防止内存溢出;
使用 jsx 语法的 render 实现
<template>
<div style="display:flex;">
<div style="flex:1">
<ul>
<my-tree :model="treeData" />
</ul>
</div>
</div>
</template>
<script>
// 导入递归组件
import MyTree from './component/myTree.vue';
export default {
name: 'App',
components: {
// 注册组件
MyTree,
},
data() {
return {
treeData: [
{
title: 'Web全栈架构师',
children: [
{
title: 'Java架构师',
},
{
title: 'JS高级',
children: [
{
title: 'ES6',
},
{
title: '动效',
},
],
},
{
title: 'Web全栈',
children: [
{
title: 'Vue训练营',
children: [
{
title: '组件化',
},
{
title: '源码',
},
{
title: 'docker部署',
},
],
},
{
title: 'React',
children: [
{
title: 'JSX',
},
{
title: '虚拟DOM',
},
],
},
{
title: 'Node',
},
],
},
],
},
],
};
},
};
</script>
<script>
export default {
props: {
model: {
type: Object,
required: true,
},
},
render() {
const toggle = menuItem => {
return () => {
menuItem.open = !menuItem.open;
};
};
const renderChildred = data => {
return data.map(child => {
return child.children && child.children.length ? (
<li>
<div onclick={toggle(child)}>
{child.title}
{child.children && child.children.length
?(<span>{child.open ? '[-]' : '[+]'}</span>)
: ('')}
</div>
{child.open ? <ul>{renderChildred(child.children)}</ul> : ''}
</li>
) : (
<li>
<div>{child.title}</div>
</li>
);
});
};
return renderChildred(this.model);
},
};
</script>
使用模板实现
<template>
<div style="display:flex;">
<div style="flex:1">
<ul v-for="treeItem in treeData" :key="treeItem">
<item-view :model="treeItem" />
</ul>
</div>
</div>
</template>
<script>
// 导入递归组件
import ItemView from './component/item.vue';
export default {
name: 'App',
components: {
// 注册组件
ItemView,
},
data() {
return {
treeData: [
{
title: 'Web全栈架构师',
children: [
{
title: 'Java架构师',
},
{
title: 'JS高级',
children: [
{
title: 'ES6',
},
{
title: '动效',
},
],
},
{
title: 'Web全栈',
children: [
{
title: 'Vue训练营',
children: [
{
title: '组件化',
},
{
title: '源码',
},
{
title: 'docker部署',
},
],
},
{
title: 'React',
children: [
{
title: 'JSX',
},
{
title: '虚拟DOM',
},
],
},
{
title: 'Node',
},
],
},
],
},
],
};
},
};
</script>
<template>
<li>
<!-- 点击折叠展开 -->
<div @click="toggle">
<!-- 显示内容 -->
{{ model.title }}
<!-- 显示折叠展开的图标,如果没有下级目录的话,则不显示 -->
<span v-if="isFolder">[{{ open ? "-" : "+" }}]</span>
</div>
<!-- 控制是否显示下级目录 -->
<ul v-show="open" v-if="isFolder">
<!-- 重点代码,调用自身,实现递归,绑定数据 -->
<item-view v-for="model in model.children" :model="model" :key="model.title" />
</ul>
</li>
</template>
<script>
export default {
name: 'ItemView',
// 如果想使用此组件,则需要传递的数据
props: {
model: {
type: Object,
required: true,
},
},
data() {
return {
// 默认不显示下级目录
open: false,
};
},
computed: {
// 控制是否有下级目录和显示下级目录
isFolder() {
return this.model.children && this.model.children.length;
},
},
methods: {
// 点击折叠展开的方法
toggle() {
if (this.isFolder) {
this.open = !this.open;
}
},
},
};
</script>
效果展示
vue2.x✍️ 异步组件
上一篇