# 递归组件
Vue递归组件通过自身调用自身来实现多层嵌套数据的渲染,递归组件实现的重要条件就是要有name名称,这样组件才能在调用自身的时候进行识别。递归组件思想就是抽取出重复出现的渲染元素,然后把这个元素拿出来封装成组件循环嵌套调用。
以创建一个无限嵌套的文件目录为例,可以把当前文件夹信息和子文件夹作为一个组件进行嵌套循环。可以用两种实现方式,一是以单个元素内容为组件封装起来,循环组件来展示。二是以一个列表做为组件封装,在内部进行循环。
递归组件向外传递事件只能传递到上一层,如需在父组件获取事件,可以使用vuex或$bus进行全局传递。
如何判断组件层级,可以在调用组件时传入一个数据,每次+1就可以判断当前层级了。
# 第一种实现方式
以子元素为循环体,子组件显示当前信息和子文件夹列表,通过children判断是否存在子文件夹。
// 父组件
<template>
<div class="index">
// 外面进行循环传入文件夹信息
<TreeItem :depth="0" :treeData="item" v-for="(item, index) in treeList" :key="index" @GET_DEPTH_EVENT="getDepth"></TreeItem>
</div>
</template>
<script>
import TreeItem from '@/components/TreeItem'
export default {
name: 'FolderList',
components: {
TreeItem
},
data() {
return {
treeList: [
{
id: 1,
label: '文件1',
children: [
{
id: 11,
label: '文件11',
children: [
{
id: 111,
label: '文件111',
children: []
},
{
id: 112,
label: '文件112',
children: []
}
]
},
{
id: 12,
label: '文件12',
children: []
},
{
id: 12,
label: '文件12',
children: []
}
]
},
{
id: 2,
label: '文件2',
children: []
}
]
}
},
methods: {
getDepth(depth) {
console.log(depth)
}
}
}
</script>
// 递归组件
<template>
<div class="tree">
<div class="tree-detail" :style="{'padding-left': depth * 20 + 'px'}" @click="toggleExpand(depth)">
<span>{{treeData.label}}</span>
</div>
<template v-if="treeData.children && treeData.children.length && isExpand">
// 递归循环体,自身调用自身,通过name进行识别,不需要引入
<TreeItem v-for="(item, index) in treeData.children" :depth="depth + 1" :treeData="item" :key="index" @GET_DEPTH_EVENT="getDepth"></TreeItem>
</template>
</div>
</template>
<script>
export default {
name: 'TreeItem',
props: {
treeData: {
type: Object,
default: () => {}
},
depth: {
type: Number
}
},
data() {
return {
isExpand: false
}
},
methods: {
toggleExpand(depth) {
this.isExpand = !this.isExpand
this.$emit('GET_DEPTH_EVENT', depth)
},
getDepth(depth) {
console.log(depth)
}
}
}
</script>
<style lang="less" scoped>
.tree {
width: 100%;
.tree-detail {
display: flex;
align-items: center;
width: 100%;
height: 40px;
}
}
</style>
# 第二种实现方式
以整个列表作为循环体,传入整个数据列表,在内部进行循环,这样就避免在父组件还要循环一遍,直接把列表数据传入就行了。
// 父组件
<template>
<div class="index">
<TreeList :treeList="treeList" :depth="0" @GET_DEPTH_EVENT="getDepth"></TreeList>
</div>
</template>
<script>
import TreeList from '@/components/TreeList'
export default {
name: 'FolderList',
components: {
TreeList
},
data() {
return {
treeList: [
{
id: 1,
label: '文件1',
children: [
{
id: 11,
label: '文件11',
children: [
{
id: 111,
label: '文件111',
children: []
},
{
id: 112,
label: '文件112',
children: []
}
]
},
{
id: 12,
label: '文件12',
children: []
},
{
id: 12,
label: '文件12',
children: []
}
]
},
{
id: 2,
label: '文件2',
children: []
}
]
}
},
methods: {
getDepth(depth) {
console.log(depth)
}
}
}
</script>
// 递归组件
<template>
<div class="tree">
<div class="tree-item" v-for="(item, index) in treeList" :key="index">
<div class="item-detail" :style="{'padding-left': depth * 20 + 'px'}" @click="toggleExpand(item)">
<span>{{item.label}}</span>
</div>
<TreeList :depth="depth + 1" :treeList="item.children" @GET_DEPTH_EVENT="getDepth"></TreeList>
</div>
</div>
</template>
<script>
export default {
name: 'TreeList',
props: {
treeList: {
type: Array,
default: () => []
},
depth: {
type: Number
}
},
methods: {
toggleExpand(depth) {
this.$emit('GET_DEPTH_EVENT', depth)
},
getDepth(depth) {
console.log(depth)
}
}
}
</script>
<style lang="less" scoped>
.tree {
width: 100%;
.tree-item {
width: 100%;
.item-detail {
display: flex;
align-items: center;
width: 100%;
height: 40px;
}
}
}
</style>
# 递归组件操作思路
首先要排除通过对props传入的直进行操作,虽然能直接改变props传入的值,应用类型也会改变原数据,但这样不好。
所以要在当前触发事件在最外层父组件进行操作原始数据,要么试着通过组件的数据双向绑定在当前组件操作进而修改原数据。
v-bind='$attrs' v-on='$listeners'
$attrs在每一层获取除props最外层传入的绑定属性,不是每一层都传的
$listeners在每一层都能获取到任意一场的提交事件
# 递归组件问题
1、两个组件之间相互嵌套调用,提示循环调用,需要提供name
使用异步组件来引入组件,把第一个组件作为原点,引入的第二个组件使用异步组件引入就可以了。
// 组件A
components: {
// 解决组件相互调用问题,使用异步组件加载组件B
AssetRelevanceDialog: () => import('@/components/common/globalAddAssetRelevanceDialog.vue')
},
← 组件使用v-model Vue动画效果 →