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'
})
  • Вопрос задан
  • 2690 просмотров
Решения вопроса 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. ...

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

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

Войти через центр авторизации
Похожие вопросы