Wygląda na to, że Vue.js 2.0 nie emituje zdarzeń od wnuczka do komponentu pradziadka.
Vue.component('parent', {
template: '<div>I am the parent - {{ action }} <child @eventtriggered="performAction"></child></div>',
data(){
return {
action: 'No action'
}
},
methods: {
performAction() { this.action = 'actionDone' }
}
})
Vue.component('child', {
template: '<div>I am the child <grand-child></grand-child></div>'
})
Vue.component('grand-child', {
template: '<div>I am the grand-child <button @click="doEvent">Do Event</button></div>',
methods: {
doEvent() { this.$emit('eventtriggered') }
}
})
new Vue({
el: '#app'
})
To JsFiddle rozwiązuje problem https://jsfiddle.net/y5dvkqbd/4/ , ale przez wyzwolenie dwóch zdarzeń:
- Jedna od wnuczka do środkowej części
- Następnie ponownie emituje ze środkowego komponentu do dziadka
Dodanie tego środkowego zdarzenia wydaje się powtarzalne i niepotrzebne. Czy istnieje sposób emisji bezpośrednio do dziadka, którego nie jestem świadomy?
źródło
Społeczność Vue generalnie preferuje używanie Vuex do rozwiązywania tego rodzaju problemów. Zmiany są wprowadzane w stanie Vuex, a reprezentacja DOM po prostu wypływa z tego, co w wielu przypadkach eliminuje potrzebę zdarzeń.
Poza tym, ponowna emisja byłaby prawdopodobnie kolejnym najlepszym wyborem, a na koniec możesz zdecydować się na użycie magistrali zdarzeń, jak opisano szczegółowo w innej wysoko ocenionej odpowiedzi na to pytanie.
Poniższa odpowiedź jest moją oryginalną odpowiedzią na to pytanie i nie jest podejściem, które podjąłbym teraz, mając większe doświadczenie z Vue.
To przypadek, w którym mógłbym nie zgodzić się z wyborem projektu Vue i uciec się do DOM.
W
grand-child
,methods: { doEvent() { try { this.$el.dispatchEvent(new Event("eventtriggered")); } catch (e) { // handle IE not supporting Event constructor var evt = document.createEvent("Event"); evt.initEvent("eventtriggered", true, false); this.$el.dispatchEvent(evt); } } }
a
parent
,mounted(){ this.$el.addEventListener("eventtriggered", () => this.performAction()) }
W przeciwnym razie tak, musisz ponownie wyemitować lub użyć autobusu.
Uwaga: dodałem kod w metodzie doEvent do obsługi IE; ten kod można wyodrębnić w sposób umożliwiający ponowne użycie.
źródło
NOWA ODPOWIEDŹ (aktualizacja z listopada 2018 r.)
Odkryłem, że możemy to zrobić, wykorzystując
$parent
właściwość składnika grand child:this.$parent.$emit("submit", {somekey: somevalue})
Dużo czystsze i prostsze.
źródło
transition
innym elemencie opakowania, a ono się zepsuje, pozostawiając w głowie wielki znak zapytania.Tak, twoje prawidłowe wydarzenia przechodzą tylko od dziecka do rodzica. Nie idą dalej, np. Od dziecka do dziadka.
Dokumentacja Vue (pokrótce) omawia tę sytuację w sekcji Komunikacja między rodzicami a dzieckiem .
Ogólna idea polega na tym, że w komponencie dziadków tworzysz pusty
Vue
komponent, który jest przekazywany od dziadków do dzieci i wnuków za pośrednictwem rekwizytów. Następnie dziadek nasłuchuje zdarzeń, a wnuki emitują zdarzenia na tej „szynie zdarzeń”.Niektóre aplikacje używają globalnej magistrali zdarzeń zamiast magistrali zdarzeń dla poszczególnych komponentów. Korzystanie z globalnej magistrali zdarzeń oznacza, że będziesz potrzebować unikalnych nazw zdarzeń lub przestrzeni nazw, aby zdarzenia nie kolidowały między różnymi komponentami.
Oto przykład implementacji prostej globalnej magistrali zdarzeń .
źródło
Inne rozwiązanie będzie włączone / emitowane w węźle głównym :
Używa
vm.$root.$emit
w wnukach , a następnie używavm.$root.$on
u przodka (lub gdziekolwiek chcesz).Zaktualizowano : czasami chciałbyś wyłączyć nasłuchiwanie w określonych sytuacjach, użyj vm. $ Off (na przykład:
vm.$root.off('event-name')
inside lifecycle hook = beforeDestroy ).Vue.component('parent', { template: '<div><button @click="toggleEventListener()">Listener is {{eventEnable ? "On" : "Off"}}</button>I am the parent - {{ action }} <child @eventtriggered="performAction"></child></div>', data(){ return { action: 1, eventEnable: false } }, created: function () { this.addEventListener() }, beforeDestroy: function () { this.removeEventListener() }, methods: { performAction() { this.action += 1 }, toggleEventListener: function () { if (this.eventEnable) { this.removeEventListener() } else { this.addEventListener() } }, addEventListener: function () { this.$root.$on('eventtriggered1', () => { this.performAction() }) this.eventEnable = true }, removeEventListener: function () { this.$root.$off('eventtriggered1') this.eventEnable = false } } }) Vue.component('child', { template: '<div>I am the child <grand-child @eventtriggered="doEvent"></grand-child></div>', methods: { doEvent() { //this.$emit('eventtriggered') } } }) Vue.component('grand-child', { template: '<div>I am the grand-child <button @click="doEvent">Emit Event</button></div>', methods: { doEvent() { this.$root.$emit('eventtriggered1') } } }) new Vue({ el: '#app' })
<script src="https://unpkg.com/vue/dist/vue.js"></script> <div id="app"> <parent></parent> </div>
źródło
Jeśli chcesz być elastyczny i po prostu nadawać wydarzenie wszystkim rodzicom i ich rodzicom rekurencyjnie aż do katalogu głównego, możesz zrobić coś takiego:
let vm = this.$parent while(vm) { vm.$emit('submit') vm = vm.$parent }
źródło
To jedyny przypadek, kiedy używam magistrali zdarzeń !! Do przekazywania danych od głęboko zagnieżdżonego dziecka, a nie bezpośrednio do komunikacji z rodzicem.
import Vue from 'vue' Vue.prototype.$event = new Vue()
this.$event.$emit('event_name', 'data to pass')
this.$event.$on('event_name', (data) => { console.log(data) })
Uwaga: jeśli nie chcesz już tego wydarzenia, wyrejestruj je:
this.$event.$off('event_name')
Nie lubię używać vuex do komunikacji między wnukiem a dziadkiem (Lub podobny poziom komunikacji).
źródło
Zrobiłem krótki mix na podstawie odpowiedzi @digout. Chcesz go umieścić przed inicjalizacją instancji Vue (nowy Vue ...), aby używać go globalnie w projekcie. Możesz go używać podobnie do normalnego wydarzenia.
Vue.mixin({ methods: { $propagatedEmit: function (event, payload) { let vm = this.$parent; while (vm) { vm.$emit(event, payload); vm = vm.$parent; } } } })
źródło
targetRef
który zatrzymuje propagację na docelowym komponencie.while
Stan zawierałby wówczas&& vm.$refs[targetRef]
- że trzeba także uwzględnić tenref
atrybut elementu docelowego w moim przypadku użycia nie trzeba tunelu aż do korzenia, oszczędzając kilka zdarzeń z wypalania i może kilka cennych nanosekund czasKomponenty VueJS 2 mają
$parent
właściwość, która zawiera ich komponent macierzysty.Ten komponent nadrzędny zawiera również własną
$parent
właściwość.Następnie, aby uzyskać dostęp do komponentu „dziadek”, wystarczy uzyskać dostęp do komponentu „rodzic rodzica”:
this.$parent["$parent"].$emit("myevent", { data: 123 });
W każdym razie jest to trochę trudne i zalecam użycie globalnego menedżera stanu, takiego jak Vuex lub podobnych narzędzi, jak powiedzieli inni respondenci.
źródło
Odrzucając odpowiedzi @kubaklam i @ digout, używam tego, aby uniknąć emisji na każdym komponencie nadrzędnym między wnukiem a (prawdopodobnie odległym) dziadkiem:
{ methods: { tunnelEmit (event, ...payload) { let vm = this while (vm && !vm.$listeners[event]) { vm = vm.$parent } if (!vm) return console.error(`no target listener for event "${event}"`) vm.$emit(event, ...payload) } } }
Podczas budowania komponentu z odległymi wnukami, w których nie chcesz, aby wiele / żadnych komponentów było powiązanych ze sklepem, ale chcesz, aby komponent główny działał jako sklep / źródło prawdy, działa to całkiem dobrze. Jest to podobne do działania w dół w dół filozofii Ember. Wadą jest to, że jeśli chcesz słuchać tego zdarzenia u każdego rodzica w międzyczasie, to nie zadziała. Ale wtedy możesz użyć $ propogateEmit, jak w powyższej odpowiedzi @kubaklam.
Edycja: początkowa maszyna wirtualna powinna być ustawiona na komponent, a nie na element nadrzędny komponentu. To znaczy
let vm = this
i nielet vm = this.$parent
źródło
Naprawdę szukam sposobu, w jaki jest to obsługiwane, tworząc klasę powiązaną z oknem i upraszczając konfigurację transmisji / słuchania, aby działała gdziekolwiek jesteś w aplikacji Vue.
window.Event = new class { constructor() { this.vue = new Vue(); } fire(event, data = null) { this.vue.$emit(event, data); } listen() { this.vue.$on(event, callback); } }
Teraz możesz po prostu odpalić / nadać / cokolwiek z dowolnego miejsca, dzwoniąc:
Event.fire('do-the-thing');
... i możesz słuchać u rodzica, dziadka, co tylko zechcesz dzwoniąc:
Event.listen('do-the-thing', () => { alert('Doing the thing!'); });
źródło