Vue组件递归


定义

组件递归即在组件的tempalte内自己调用自己,需要设置组件的name属性

常用于不确定层级数量的独立组件,如级联选择器、树状结构等

简单实现

简单的递归组件如下:

template部分

1
2
3
<div v-for="(item, index) in dataList">
<tree :dataList="item.children"></tree>
</div>

script部分

1
2
3
4
5
6
7
8
9
10
export default {
// 必须写 name !!!
name: 'tree',
prop: {
dataList: {
type: Array,
default: () => []
}
}
}

实例

需求:结合 element-ui,封装一个基于 el-menu 的递归组件

实现效果:传入一个嵌套数组数据,依照数据的children数组属性实现多级数据渲染,且包含除第一级菜单以外均可能包含一个article数组,需要同时渲染,children内为子节点,只可展开不可点击,article
内为最终可点击的列表项。效果图如下:
实现效果图

代码如下:

外层组件TreeMenu(包裹递归组件)

template部分

1
2
3
4
5
6
7
8
9
10
11
12
<el-menu v-if="list && list.length" :default-active="defaultActive" unique-opened :router="router">
<menu-item
:list="list"
:base-path="basePath"
:children-attr-name="childrenAttrName"
:key-id-attr-name="keyIdAttrName"
:title-attr-name="titleAttrName"
:articles-attr-name="articlesAttrName"
:article-key-id-attr-name="articleKeyIdAttrName"
:article-title-attr-name="articleTitleAttrName"
></menu-item>
</el-menu>

script部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import MenuItem from './MenuItem'

export default {
name: 'TreeMenu',
components: {
MenuItem
},
props: {
// 是否开启路由
router: {
type: Boolean,
default: false
},
// 数据数组
list: {
type: Array,
default: () => []
},
// 默认打开
defaultActive: {
type: String
},
// 基础路径,允许为空
basePath: {
type: String
},
// 主节点子节点数组属性名
childrenAttrName: {
type: String,
default: 'children'
},
// 主节点id属性名
keyIdAttrName: {
type: String,
default: 'newsCategoryId'
},
// 主节点标题属性名
titleAttrName: {
type: String,
default: 'newsCategoryName'
},
// 文章数组属性名
articlesAttrName: {
type: String,
default: 'articles'
},
// 文章项id属性名
articleKeyIdAttrName: {
type: String,
default: 'newsId'
},
// 文章项标题属性名
articleTitleAttrName: {
type: String,
default: 'newsTitle'
}
},
// 由于外界可能是动态传入此参数,故写在watch内
watch: {
defaultActive(val) {
// 当前路由链接与传入连接一致时,不再跳转,防止跳转相同路径报错
if (this.$route.path !== val) {
this.$router.push(val)
}
}
}
}
递归组件MenuItem

template部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div>
<!--遍历第一层类目-->
<template v-for="item of list">
<el-submenu :key="item[keyIdAttrName]" :index="`${basePath}/${item[keyIdAttrName]}`">
<span slot="title">{{ item[titleAttrName] }}</span>
<!--递归遍历第二层-->
<menu-item
:list="item[childrenAttrName]"
:base-path="basePath"
:children-attr-name="childrenAttrName"
:key-id-attr-name="keyIdAttrName"
:title-attr-name="titleAttrName"
:articles-attr-name="articlesAttrName"
:article-key-id-attr-name="articleKeyIdAttrName"
:article-title-attr-name="articleTitleAttrName"
></menu-item>
<!--遍历第一层类目文章-->
<el-menu-item v-for="article of item[articlesAttrName]" :key="article[articleKeyIdAttrName]"
:index="`${basePath}/${article[articleKeyIdAttrName]}`">{{ article[articleTitleAttrName] }}
</el-menu-item>
</el-submenu>
</template>
</div>

script部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
export default {
name: 'MenuItem',
props: {
// 数据数组
list: {
type: Array,
default: () => []
},
// 基础路径,允许为空
basePath: {
type: String,
},
// 主节点子节点数组属性名
childrenAttrName: {
type: String,
default: 'children'
},
// 主节点id属性名
keyIdAttrName: {
type: String,
default: 'newsCategoryId'
},
// 主节点标题属性名
titleAttrName: {
type: String,
default: 'newsCategoryName'
},
// 文章数组属性名
articlesAttrName: {
type: String,
default: 'articles'
},
// 文章项id属性名
articleKeyIdAttrName: {
type: String,
default: 'newsId'
},
// 文章项标题属性名
articleTitleAttrName: {
type: String,
default: 'newsTitle'
}
}
}