首页 > 解决方案 > 点击时触发命名组件并管理点击关闭元素

问题描述

我正在创建一个Dropdown.vue组件。它有一个名为的道具name,以便我可以使用我想要的任何 div 通过点击事件触发它。

例如,我有一个元素<button @click="openDropdown('notifications-dropdown')">test</test>

然后我有组件:<dropdown name="notifications-dropdown"><div>content</div></dropdown>

click 事件是从全局 Vue 对象的方法中处理的:

methods: {
        openDropdown( name) {
            EventBus.$emit('dropdown-opened', name);
        }
    }

在实际的 Dropdown 组件上,我使用了 Mounted 方法:

EventBus.$on('dropdown-opened', (name) => {
 if (this.name == name) {
    this.active = true;
 }
});

这一切都很好,但是,我需要处理单击关闭元素的功能,以便当没有单击下拉菜单时,active布尔值更改为 false。

在下拉列表中,我有:

methods: {
        closeDropdown(e) {
            if ( !this.$el.contains(e.target) ) {
                this.active = false;
            } 
        }
    }

mountedand中beforeDestroy,我有:

document.removeEventListener('click', this.closeDropdown)

然后问题就变成了,在触发下拉菜单的初始按钮单击时,也会触发事件单击关闭元素。所以下拉菜单打开并立即关闭

如何在触发下拉菜单的初始按钮单击时做到这一点也不会触发 off e.targetclick closeDropdown()

但是,如果您再次单击触发器,我希望下拉菜单会关闭。

以下是完整代码:

Dropdown.vue

<template>
    <Transition name="fade-in-scale">
        <div @click.stop ref="thedropdown" v-if="active" class="dropdown-menu absolute bg-white border-l border-b border-r border-gray-200 top-100  w-72 right-0 md:w-84">
            <slot></slot>
        </div>
    </Transition>
</template>

<script>
// @ is an alias to /src

export default {

    name: 'Dropdown',
    data() {
        return {
            active: false
        }
    },
    props: {
        name: String
        
    },
    computed: {
      
    },
    mounted() {
        document.addEventListener('click', this.closeDropdown);

        EventBus.$on('dropdown-opened', (name) => {
            if (this.name == name) {
                this.active = !this.active;
            }
        });
       
    },

    beforeDestroy () {
        document.removeEventListener('click', this.closeDropdown)
    },

    watch: {
        
    },
    methods: {
        closeDropdown(e) {
            if ( !this.$el.contains(e.target) ) {
                this.active = false;
            } 
        }
    }
}
</script>


<style lang="scss" scoped>

    .dropdown-menu {
        top:calc(100% + 5px);
    }

    .fade-in-scale-enter-active,
    .fade-in-scale-leave-active {
        transition: all 0.12s;
        transform:scale(1) ;
        opacity:1;
    }
    .fade-in-scale-enter,
    .fade-in-scale-leave-to {
        transform: scale(0.90);
        opacity: 0;
    }
    
</style>

app.blade.php

<button @click="openDropdown('notifications-dropdown')" type="button" class="p-2 outline-none focus:outline-none"></button>
<dropdown name="notifications-dropdown"></dropdown>

app.js

require('./bootstrap');

/**
 * UI
 */
import Btn from './components/ui/Button.vue';
import Dropdown from './components/ui/Dropdown.vue';

const app = new Vue({
    el: '#app',
    components: {
       Btn,
       Dropdown
    },
    methods: {
        openDropdown( name) {
            EventBus.$emit('dropdown-opened', name);
        }
    }
});

标签: vue.js

解决方案


在打开按钮上使用.stop事件修饰符@click,以便事件的传播停止并且不会到达文档的click处理程序:

<button @click.stop="openDropdown(...)">

演示

还要注意 中的内存泄漏Dropdown.vue,因为它不会从事件总线中删除事件侦听器。这很容易通过声明一个组件方法来解决openDropdown,并使用它来添加事件侦听器mounted并在其中删除它beforeDestroy

export default {
    mounted() {
        EventBus.$on('dropdown-opened', this.openDropdown);
    },

    beforeDestroy () {
        EventBus.$off('dropdown-opened', this.openDropdown)
    },

    methods: {
        openDropdown(name) {/*...*/}
    }
}

推荐阅读