前言
今天在写项目时,遇到一个自定义右键菜单的需求。在菜单中还有子菜单,所以这个时候就要用到递归组件了。所以写下这篇文章来记录一下自己编写递归组件的过程。
1、递归组件
递归组件,顾名思义就是在组件本身内部调用自身。所以我们先构建一个组件,并在自身内部调用自身。常见的递归组件就是我们项目中经常会用到的树组件了。下面就是我自己实现的一个能够满足项目需求的递归组件的源码。
<template>
<ul class="list-container">
<li v-for="(item,index) in listData"
:key="index" class="list-item"
@click.prevent.stop="handleClick($event,item)"
@mouseover="childrenMenuIndex=index"
>
<span class="list-item_span">
{{item.text}}
</span>
<CaretRightOutlined v-if="item.children" />
<!-- 判断是否需要调用自身 -->
<div v-if="item.children&&childrenMenuIndex===index"
class="context-menu context-menu_children"
>
<!-- 在组件自身内部调用自身 -->
<list-comp :list-data='item.children' @hideContextMenu='hideContextMenuEvent' />
</div>
</li>
</ul>
</template>
<script>
import { defineComponent, ref } from "vue";
import {CaretRightOutlined} from '@ant-design/icons-vue';
export default defineComponent({
name:'list-comp',
props:{
listData:{
type:Array,
default:()=>[]
}
},
components:{
CaretRightOutlined
},
emits:[
"hideContextMenu"
],
setup(props,{emit}){
//点击事件
const handleClick=(event,{text,callBack})=>{
emit('hideContextMenu');
//callBack是你自己传进来的回调函数,如果传入了,则调用自定义回调函数
if(callBack){
callBack();
return;
}
}
const hideContextMenuEvent=()=>{
emit('hideContextMenu');
}
//用于标识当前选中的菜单项
const childrenMenuIndex=ref(-1);
const eventNames=['click','contextmenu'];
onMounted(()=>{
eventNames.forEach(eventName=>window.addEventListener(eventName,hideContextMenuEvent))
})
onBeforeUnmount(()=>{
eventNames.forEach(eventName=>window.removeEventListener(eventName,hideContextMenuEvent))
})
return {
handleClick,
childrenMenuIndex,
hideContextMenuEvent
}
}
})
</script>
注意事项
- 在递归组件本身内部,调用自身时,需要将在递归组件上接收自己通过emit发出的自定义事件,接收后,在组件内部再次通过emit触发自定义事件。
- 通过监听click事件,可以通过emit触发自定义事件,在组件外部监听;也可以直接在通过 props传递数据到组件内部时,就自己先构建好回调,这样就可以不用通过emit触发自定义事件了。
- 在点击递归组件中的菜单项时,需要让递归组件销毁。所有我们需要在 递归组件内通过事件冒泡 监听click,contextmenu等事件来让组件销毁,然后通过emit触发自定义事件,让外界接收,从而达到销毁组件的目的。
- 在递归组件内部调用click事件时,需要阻止事件冒泡以及默认事件。可以在click事件后面添加click.prevent.stop来阻止事件冒泡和默认事件。
2、右键菜单组件
我项目中使用的是组件的形式来实现右键菜单菜单的。当然也可以通过插件的形式来实现。我这里的右键菜单本质上就是对递归组件 的二次封装,其实不用二次封装也可以,可以直接使用递归组件作为右键菜单。
<template>
<teleport to='body' >
<div class="content-menu_container" :style="styleObj">
<list-comp
:list-data='menuData'
@hideContextMenu='windowClickHandler'
/>
</div>
</teleport>
</template>
<script>
import { defineComponent } from "vue";
import ListComp from "./list-comp.vue"
export default defineComponent({
name:"contextMenu",
components:{
ListComp
},
props:{
styleObj:{
type:Object,
default:()=>{}
},
menuData:{
type:Array,
default:()=>[]
}
},
emits:['closeContextMenu'],
setup(props,{emit}){
const windowClickHandler=()=>{
emit('closeContextMenu')
};
return {
windowClickHandler,
}
}
})
</script>
注意事项
在项目中调用右键菜单时,需要先禁用掉window自身的右键菜单事件。然后实现自己的自定义菜单事件。实现代码如下所示。
const showContextMenu=(event)=>{
//禁用默认事件和阻止冒泡
event.stopPropagation();
event.preventDefault();
state.showContextMenu=true;
state.styleObj={
left:event.clientX+ "px",
top:event.clientY+'px'
}
}
//监听window自身的右键菜单事件
onMounted(()=>{
window.addEventListener('contextmenu',showContextMenu)
})
onBeforeUnmount(()=>{
window.removeEventListener('contextmenu',showContextMenu)
})
总结
到此这篇关于vue3递归组件封装的文章就介绍到这了,更多相关vue3递归组件封装内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论