#vue.js
Вопрос:
Я создаю Dropdown.vue
компонент. У него есть опора, называемая name
так, чтобы я мог запустить ее с помощью любого div, который я хочу, с помощью события щелчка.
Например, у меня есть элемент <button @click="openDropdown('notifications-dropdown')">test</test>
тогда у меня есть компонент: <dropdown name="notifications-dropdown"><div>content</div></dropdown>
Событие щелчка обрабатывается из глобального объекта Vue в методах:
methods: {
openDropdown( name) {
EventBus.$emit('dropdown-opened', name);
}
}
На фактическом раскрывающемся компоненте у меня есть в смонтированном методе:
EventBus.$on('dropdown-opened', (name) => {
if (this.name == name) {
this.active = true;
}
});
Все это работает нормально, однако мне нужно обработать функциональность элемента click off, чтобы, когда выпадающий список не щелкается, active
логическое значение было изменено на false.
в раскрывающемся списке у меня есть:
methods: {
closeDropdown(e) {
if ( !this.$el.contains(e.target) ) {
this.active = false;
}
}
}
и в mounted
и beforeDestroy
, у меня есть:
document.removeEventListener('click', this.closeDropdown)
Затем возникает проблема: при первом нажатии кнопки, которая запускает раскрывающийся список, также запускается событие click off элемента. таким образом, раскрывающийся список открывается и сразу же закрывается
Как я могу сделать так, чтобы при первом нажатии кнопки, которая запускает выпадающий список, также не запускался e.target
щелчок closeDropdown()
выключателя .
Но затем, если вы снова нажмете на спусковой крючок, я ожидаю, что выпадающий список закроется.
Below is the full code:
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);
}
}
});
Ответ №1:
Используйте .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) {/*...*/}
}
}
Комментарии:
1. Идеально! Большое спасибо. Я беспокоился, что мне придется переосмыслить то, как я все делаю. Мне придется разобраться с утечками памяти и Vue. Я добавил методы mounted и beforeDestroy. Спасибо, спасибо тебе!
2. Почему вы используете шину событий для открытия раскрывающегося списка? Я бы просто поместил ссылку на шаблон в раскрывающемся списке, чтобы вызвать его
openDropdown
метод.3. Изначально я создавал отдельный компонент для каждого выпадающего списка. Я возвращаюсь и рефакторингую компоненты, чтобы элементы могли просто вызывать выпадающий список, в котором используются слоты. Можете ли вы показать мне пример того, что вы имеете в виду? Я использовал шину только потому, что не знаю другого способа глобального вызова определенного экземпляра компонента. все еще довольно новичок в vue и у него есть вредные привычки
4. Ограниченные слоты — это определенно правильный путь.
5. Быстрый рефакторинг состоял бы в том, чтобы родитель поместил ссылку на шаблон в раскрывающийся список (
<dropdown ref="dropdown">
) и вызвал егоopenDropdown
метод из обработчика кнопки открытияclick
(<button @click="$refs.dropdown.openDropdown()">Open</button>
)