mrswylet
@mrswylet

Можли ли продолжить всплытие собственных событий в vue.js?

У vue.js есть функционал, который позволяет передать родительскому компоненту собственно событие, например $emit('selected-menu', event). Но это событие можно прослушать только на родительском компоненте при вызове дочернего.
А можно ли как то прокинуть это событие далее(всплытие), что бы можно было его прослушать на любом компоненте-предке, в том числе и на корневом?

Пример кода: корневой компонент, тут не получается прослушать событие, не отрабатывает console.log('я </main-header></main-header>')

Vue.component('mian-container', {
	template: `<main-header @selected-menu="getEvent"></main-header>`,
	methods:{
		getEvent(event){
			console.log('я </main-header></main-header>')
		}
	}
})

Это компонент <main-header>, тут я успешно прослушиваю событие, выводится console.log('я </main-header></main-header>')

Vue.component('main-header', {
	template: `<main-menu @selected-menu="getEvent"></main-menu>`,
	methods:{
		getEvent(event){
			console.log('я <main-menu></main-menu>')
		}
	}
})

Это компонент <main-menu>, тут я генерирую событие с помощью this.$emit('selected-menu', event);

Vue.component('main-menu', {
	template: `<ul @click="selectedMenu"> <li>Главная</li> <li>Проекты</li> </ul>`,
	methods:{
		selectedMenu(event){
			let el = event.target.closest('li');
			if(!el) {return}
			event.my_data = { el: el }
			this.$emit('selected-menu', event);
		}
	}
})

Создание экземпляра vue
new Vue({
	el: '#main'
})
  • Вопрос задан
  • 2834 просмотра
Решения вопроса 3
Xuxicheta
@Xuxicheta
инженер
Прокинуть на 1 уровень вверх: https://jsfiddle.net/6v6qhq00/40/

А можно ли как то прокинуть это событие далее(всплытие), что бы можно было его прослушать на любом компоненте-предке, в том числе и на корневом?

если только
this.$root.emit('selected-menu', data) 
this.$root.on('selected-menu', callback) // и не забыть отписаться

А вообще для обмена данными между компонентами есть vuex
Ответ написан
0xD34F
@0xD34F Куратор тега Vue.js
А можно ли как то прокинуть это событие далее(всплытие), что бы можно было его прослушать на любом компоненте-предке, в том числе и на корневом?

Вручную в каждом компоненте делайте emit, или гуглите, как сделать шину событий (event bus) - можно будет подписываться на нужное событие где угодно.
Ответ написан
mrswylet
@mrswylet Автор вопроса
Если кто-то пришел сюда со страницы поисковика, то вот готовый ответ. (Текста будет много)
(Скажу сразу, я нет могу гарантировать, что все написанной мной абсолютная истина, но код рабочий)
Есть для варианта пробросить шину событий (event bus). У каждого есть плюсы и минусы. Сейчас рассмотрим оба.

1. Проброс event bus через корневой компонент $root.
Vue.component('mian-container', {
	template: `<main-header ></main-header>`,
	data(){ 
		return{ 
			name: 'я <<main-header></<main-header>' 
		} 
	},
	// тут мы при создании компонента подписываем его на прослушку события 'selected-menu'
	created(){ 
		this.$root.$on('selected-menu', function (event) { 
			console.log(event.target + this.name) 
		}.bind(this)) // не забыть про .bind(this), иначе произойдет потеря контекста
	} 
})
Vue.component('main-header', {
	template: `<main-menu ></main-menu>`,
	data(){ 
		return{ 
			name: 'я <main-menu></main-menu>' 
		} 
	},
	// тут мы при создании компонента подписываем его на прослушку события 'selected-menu'
	created(){ 
		this.$root.$on('selected-menu', function (event) { 
			console.log(event.target + this.name)  
		}.bind(this)) // не забыть про .bind(this), иначе произойдет потеря контекста
	}
})
Vue.component('main-menu', {
	template: `<button @click="selectedMenu">Меню</button>`,
	methods:{
		selectedMenu(event){
			// тут мы создаем событие 
			this.$root.$emit('selected-menu', event)
		}
	}
})
new Vue({
	el: '#main'
})


1. Проброс event bus через дополнительный экземпляр Vue, например bus.
// тут создаем дополнительный экземпляр Vue, 
// далее по коду используем его вместо 'this.$root'
let bus = new Vue();

Vue.component('mian-container', {
	template: `<main-header ></main-header>`,
	data(){ 
		return{ 
			name: 'я <<main-header></<main-header>' 
		} 
	},
	created(){ 
		bus.$on('selected-menu', function (event) { 
			console.log(event.target + this.name) 
		}.bind(this))
	} 
})
Vue.component('main-header', {
	template: `<main-menu ></main-menu>`,
	data(){ 
		return{ 
			name: 'я <main-menu></main-menu>' 
		} 
	},
	created(){ 
		bus.$on('selected-menu', function (event) { 
			console.log(event.target + this.name)  
		}.bind(this))
	}
})
Vue.component('main-menu', {
	template: `<button @click="selectedMenu">Меню</button>`,
	methods:{
		selectedMenu(event){
			bus.$emit('selected-menu', event)
		}
	}
})
new Vue({
	el: '#main'
})


Какой вариант выбрать, решать конечно же вам. От себя могу добить следующее:
1) способ через корневой компонент $root
(+) более лаконичный, если вы работаете с системами сборки, например webpack, то вам не придется думать о подключении дополнительного экземпляра Vue в каждый файл
(-) если у вам очень много "глобальных" событий, то будут проблемы с производительность

2) способ через дополнительный экземпляр Vue
(+) этот вариант менее чувствительный к большему количеству событий
(-) если вы работаете с системами сборки, например webpack, то вам придется помнить о подключении дополнительного экземпляра Vue в каждый файл где вы будете использовать event bus
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@pal-software
Начал изучать Vue и буквально сегодня с такой же проблемой столкнулся. Потыкался, почитал, ничего не нашел, в итоге сам запилил такую штуку.. (решение может и не ахти какое, но вроде все работает):
1. в основной javascript файл в начало вставляем такой код:
Vue.prototype.$popup_emit = function (event) {
    var vm = this;
    var args = toArray(arguments, 1);

    while ( vm && vm != null )
    {
        var cbs    = vm._events[event];

        if (cbs) {
            cbs = cbs.length > 1 ? toArray(cbs) : cbs;

            for (var i = 0, l = cbs.length; i < l; i++) {
                try {
                    var result = cbs[i].apply(vm, args);

                    // если обработчик вернет false, то сразу останавливаем обработку событий
                    if ( result === false )
                    {
                        return this;
                    }

                } catch (e) {
                    handleError(e, vm, ("event handler for \"" + event + "\""));
                }
            }
        }
        vm = vm.$parent;
    }
    return this;

    function toArray (list, start) {
        start = start || 0;
        var i = list.length - start;
        var ret = new Array(i);
        while (i--) {
          ret[i] = list[i + start];
        }
        return ret
      }
};

(по сути это исходник стандартного метода emit с некоторыми моими изменениями)

2. вместо this.$emit(...) вызываем теперь this.$popup_emit(...) ... это событие будет всплывать от дочерних компонентов к родительским пока не дойдет до самого верха или пока один из обработчиков не вернет false. ...

ну а дальше можно допиливать на свое усмотрение. ...сильно не тестировал, может какие ошибки, но идея, думаю, понятна..
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы