While writing a project today, I came across a need for a custom right-click menu. There are submenus within the menu, so this is where recursive components come in. So write this article to document the process of writing your own recursive components.

1. Recursive components

A recursive component, as its name implies, calls itself from within the component itself. So we start by building a component and calling itself within itself. The most common recursive component is the tree component that we often use in our projects. Below is my own implementation of a recursive component to meet the needs of the project source code.

<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"  />
           <! -- Determine if you need to call itself -->
           <div v-if="item.children&&childrenMenuIndex===index"
            class="context-menu context-menu_children"
           >
            <! Call itself inside the component itself -->
            <list-comp :list-data='item.children' @hideContextMenu='hideContextMenuEvent' />
           </div>
       </li>
   </ul>
</template>
Copy the code
<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}){
    	// Click the event
        const handleClick=(event,{text,callBack}) = >{
            emit('hideContextMenu');
            //callBack is the callBack you pass in, and if it is passed in, the custom callBack is called
            if(callBack){
                callBack();
                return; }}const hideContextMenuEvent=() = >{
            emit('hideContextMenu');    
        }
        // Identifies the currently selected menu item
        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>
Copy the code

Matters needing attention

  • Within the recursive component itself, when calling itself, it needs to receive itself through on the recursive componentemitEmitted custom events that are received and passed again within the componentemitTriggers a custom event.
  • By listeningclickEvents can be passedemitTrigger custom events that listen outside the component; It can also be directly throughpropsWhen passing data inside a component, you build your own callbacks so that you don’t have to passemitThe custom event is triggered.
  • When a menu item in a recursive component is clicked, the recursive component needs to be destroyed. All we need in theBubbling through events within recursive componentsListening to theclick,contextmenuWait for the event to destroy the component and then pass throughemitTriggers a custom event that is received by the outside world to destroy the component.
  • Called within a recursive componentclickEvent, you need to prevent event bubbling and default events. Can be found inclickAdd after the eventclick.prevent.stopTo prevent event bubbling and default events.

2. Right-click the menu component

My project is used in the form of components to achieve the right menu menu. Of course, it can also be implemented in the form of plug-ins. My right-click menu here is essentially a second wrapper for a recursive component. You can use the recursive component directly as the right-click menu without the second wrapper.


<template>
    <teleport to='body' >
        <div class="content-menu_container" :style="styleObj">
            <list-comp 
                :list-data='menuData'
                @hideContextMenu='windowClickHandler'
             />
        </div>
    </teleport>
</template>
Copy the code
<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>
Copy the code

Matters needing attention

  • When calling the right-click menu in a project, you need to disable it firstwindowIts own right-click menu event. Then implement your own custom menu events. The implementation code is shown below.
const showContextMenu=(event) = >{
    // Disable default events and prevent bubbling
    event.stopPropagation();
    event.preventDefault();
    state.showContextMenu=true;
    state.styleObj={
      left:event.clientX+ "px".top:event.clientY+'px'}}// Listen for the window's own right-click menu event
  onMounted(() = >{
    window.addEventListener('contextmenu',showContextMenu)
  })
  onBeforeUnmount(() = >{
    window.removeEventListener('contextmenu',showContextMenu)
  })
Copy the code

[Shanghai] Meituan – In-store platform Technology Department/in-store comprehensive front-end research and development

Resume should be sent to [email protected] (email format: name – position)

The project code address is as follows. If you find it helpful, welcome star.