vue2、vue3如何递归组件,自制树形tree组件
前言
当遇到树形结构数据时,返回了不确定层次的树形数据格式,需要将数据渲染出来如何自制封装一个组件来显示
例如拥有这样的tree树形数据结构
const arr = [
{
label: '第一层数据1',
value: 1,
children: [
{
label: '第二层数据1',
value: 2,
children: [
{
label: '第三层数据1',
value: 4,
children: []
}
]
},
{
label: '第二层数据2',
value: 3,
children: []
},
]
},
{
label: '第一层数据2',
value: 5,
children: []
}
]
一般情况下,遇到树形结构数据使用递归,如果是组件需要递归显示,那在vue中如何实现呢
下面讲解vue2、vue3的递归用法并封装一个简易demo
vue2
在vue2中,封装一个简易的递归树形Tree组件,用于展示
1、使用name属性定义组件名,然后在组件内调用自身
在vue中的 name
属性的作用:
1、在递归组件的时候需要定义name
2、配合keep-alive include exclude 可以缓存组件
3、在Vue有报错或者调试的时候可以看到组件的name
<template>
<div class="MyTree">
<!-- 调用自己自身形成递归 -->
<MyTree />
</div>
</div>
</template>
<script>
export default {
name: 'MyTree',
};
</script>
2、从外部传入树形数据,判定终止条件,避免无限递归造成内存溢出
<template>
<div class="MyTree">
<div v-for="item in nodeData" :key="item.value" class="MyTree-item">
<span>{{ item.label }}</span>
<MyTree v-if="item.children && item.children.length > 0" :node-data="item.children" />
</div>
</div>
</template>
<script>
export default {
name: 'MyTree',
props: {
nodeData: {
type: Array,
default: () => ([])
}
},
};
</script>
使用组件
<Tree :node-data="arr" />
最核心的就完成了,接下来可以增加一些扩展功能
3、获取当前层级,点击当前节点行返回数据
对循环的item增加点击事件 toCurrentNode
methods: {
// 点击整行事件,向父组件返回当前节点和当前层级
toCurrentNode(item) {
const node = {
...item,
level: this.getLevel()
}
this.$emit('currentNode', node)
console.log(node, 'node')
},
// 根据父节点层数判断当前层级
getLevel() {
let level = 1;
let parent = this.$parent;
while (parent && parent.$options.name === 'MyTree') {
level++;
parent = parent.$parent;
}
return level;
}
}
4、展开收起子节点功能
对组件增加 expandedkeys
数组参数,根据传入 唯一值
,当子节点判定该数组存在 唯一值
时,展开显示子节点,并且加入vue过渡效果
完整代码如下:
<template>
<div class="MyTree">
<div v-for="item in nodeData" :key="item.value" class="MyTree-children">
<div class="MyTree-item" @click.stop="toCurrentNode(item)">
<!-- 没有子节点数据不显示箭头 -->
<template v-if="item.children && item.children.length > 0">
<span v-if="!expandedkeysArray.includes(item.value)">→</span>
<span v-else>↓</span>
</template>
<span>{{ item.label }}</span>
</div>
<!-- children有值 && expandedkeysArray数组存在该value值时再展开显示 -->
<transition name="expand">
<MyTree v-if="item.children && item.children.length > 0 && expandedkeysArray.includes(item.value)" :node-data="item.children" :style="{marginLeft: `${getLevel() * 20}px`}" />
</transition>
</div>
</div>
</template>
<script>
export default {
name: 'MyTree',
props: {
nodeData: {
type: Array,
default: () => ([])
},
expandedkeys: {
type: Array,
default: () => ([])
},
},
computed: {
// 实时更新父组件的值
expandedkeysArray: {
get() {
return this.expandedkeys
},
set(newVal) {
this.$emit('update:expandedkeys', newVal)
}
}
},
methods: {
// 点击整行事件,向父组件返回当前节点和当前层级
toCurrentNode(item) {
const node = {
...item,
level: this.getLevel()
}
this.$emit('currentNode', node)
// 没有展开的子节点才push展开
if (!this.expandedkeysArray.includes(item.value)) {
this.expandedkeysArray.push(item.value)
return
}
// 已经展开的子节点删除收起
const index = this.expandedkeysArray.indexOf(item.value)
if (index !== -1) {
this.expandedkeysArray.splice(index, 1)
}
},
// 根据父节点层数判断当前层级
getLevel() {
let level = 1;
let parent = this.$parent;
while (parent && parent.$options.name === 'MyTree') {
level++;
parent = parent.$parent;
}
return level;
}
}
};
</script>
<style lang="less" scoped>
.MyTree{
.MyTree-item{
cursor: pointer;
border-bottom: 1px solid #E4E7ED;
display: flex;
align-items: center;
padding: 5px 10px;
&:hover{
background: #F2F6FC;
}
}
}
.expand-enter-active, .expand-leave-active {
transition: all 0.3s;
}
.expand-enter, .expand-leave-to {
transform: scaleY(0);
transform-origin: top;
opacity: 0;
}
</style>
使用组件
<Tree :node-data="arr" :expandedkeys.sync="expandedKeys" @currentNode="currentNode" />
data() {
expandedKeys: [1, 5]
}
效果如下
还可以继续扩展,例如增加复选框选择,自定义传入参数键名等,这里暂不做过多操作,核心展示如何递归组件,包括递归第三方UI库的组件也是可以的
vue3
在vue3中,由于不使用选项式API,改用了组合式API时,若想要递归组件,组件的name属性该如何定义呢
有如下几种方式
1、自动根据文件名生成对应的 name
选项
在vue3中,使用了 <script setup>
语法糖模式会自动根据文件名生成对应的 name
选项
上述的组件名分别为 Index、HelloWorld、VModel
此方式不适用 文件夹/index.vue结构,例如 MyTree/index.vue,组件名称为Index
2、再多使用一个 <script>
标签来定义name
<script lang="ts" setup>
</script>
<script>
export default {
name: "MyTree"
}
</script>
3、使用插件 unplugin-vue-define-options
<script setup lang="ts">
defineOptions({
name: 'MyTree',
})
</script>
不过这个插件的原理实际也是和方案2一样再加上一个
4、组件内引入自身文件进行递归(只适用于递归组件)
由于vue3的组件变为通过 变量引用
而不是 基于字符串组件名
注册的,所以可以引用自身文件然后直接使用也可以形成递归
代码如下
<template>
<div>
myTree
<MyTree />
</div>
</template>
<script lang="ts" setup>
import MyTree from './index.vue'
</script>
转载自:https://juejin.cn/post/7271592008342831167