Il semble que Vue.js 2.0 n'émet pas d'événements d'un petit-enfant à son composant grand-parent.
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'
})
Ce JsFiddle résout le problème https://jsfiddle.net/y5dvkqbd/4/ , mais en émettant deux événements:
- Un du petit-enfant au composant intermédiaire
- Puis émettant à nouveau du composant intermédiaire au grand parent
L'ajout de cet événement intermédiaire semble répétitif et inutile. Existe-t-il un moyen d'émettre directement aux grands-parents dont je ne suis pas au courant?
la source
La communauté Vue privilégie généralement l'utilisation de Vuex pour résoudre ce type de problème. Des modifications sont apportées à l'état de Vuex et la représentation DOM en découle simplement, éliminant le besoin d'événements dans de nombreux cas.
À part cela, réémettre serait probablement le meilleur choix suivant, et enfin, vous pourriez choisir d'utiliser un bus événementiel comme détaillé dans l'autre réponse hautement votée à cette question.
La réponse ci-dessous est ma réponse originale à cette question et n'est pas une approche que je prendrais maintenant, ayant plus d'expérience avec Vue.
C'est un cas où je pourrais être en désaccord avec le choix de conception de Vue et recourir au DOM.
Dans
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); } } }
et dans
parent
,mounted(){ this.$el.addEventListener("eventtriggered", () => this.performAction()) }
Sinon, oui, vous devez réémettre ou utiliser un bus.
Remarque: j'ai ajouté du code dans la méthode doEvent pour gérer IE; ce code pourrait être extrait de manière réutilisable.
la source
NOUVELLE RÉPONSE (mise à jour nov-2018)
J'ai découvert que nous pouvions le faire en tirant parti de la
$parent
propriété du composant grand-enfant:this.$parent.$emit("submit", {somekey: somevalue})
Beaucoup plus propre et plus simple.
la source
transition
ou tout autre composant de wrapper et il se cassera, vous laissant avec un gros point d'interrogation dans votre tête.Oui, vous avez raison, les événements ne vont que d'un enfant à l'autre. Ils ne vont pas plus loin, par exemple d'enfant en grand-parent.
La documentation Vue aborde (brièvement) cette situation dans la section Communication non parent-enfant .
L'idée générale est que dans le composant grands-parents, vous créez un
Vue
composant vide qui est transmis des grands-parents aux enfants et petits-enfants via des accessoires. Le grand-parent écoute alors les événements et les petits-enfants émettent des événements sur ce «bus d'événements».Certaines applications utilisent un bus d'événements global au lieu d'un bus d'événements par composant. L'utilisation d'un bus d'événements global signifie que vous devrez avoir des noms d'événements ou un espace de noms uniques pour que les événements n'entrent pas en conflit entre les différents composants.
Voici un exemple de mise en œuvre d'un bus d'événements global simple .
la source
Une autre solution sera activée / émise au nœud racine :
S'utilise
vm.$root.$emit
chez petit-enfant , puis utilisevm.$root.$on
chez l'ancêtre (ou partout où vous le souhaitez).Mise à jour : parfois, vous souhaitez désactiver l'écouteur dans certaines situations spécifiques, utilisez vm. $ Off (par exemple:
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>
la source
Si vous voulez être flexible et simplement diffuser un événement à tous les parents et à leurs parents de manière récursive jusqu'à la racine, vous pouvez faire quelque chose comme:
let vm = this.$parent while(vm) { vm.$emit('submit') vm = vm.$parent }
la source
C'est le seul cas où j'utilise le bus événementiel !! Pour transmettre des données d'un enfant imbriqué profond à une communication non directement parent.
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) })
Remarque: si vous ne voulez plus cet événement, veuillez le désinscrire:
this.$event.$off('event_name')
Je n'aime pas utiliser vuex pour la communication grand-enfant à grand-parent (ou niveau de communication similaire).
la source
J'ai fait un court mixin basé sur la réponse @digout. Vous voulez le mettre, avant l'initialisation de votre instance Vue (nouvelle Vue ...) pour l'utiliser globalement dans le projet. Vous pouvez l'utiliser de la même manière qu'un événement normal.
Vue.mixin({ methods: { $propagatedEmit: function (event, payload) { let vm = this.$parent; while (vm) { vm.$emit(event, payload); vm = vm.$parent; } } } })
la source
targetRef
qui arrête la propagation sur le composant que vous ciblez. Lawhile
condition inclurait alors&& vm.$refs[targetRef]
- vous auriez également besoin d'inclure cetref
attribut sur le composant ciblé.Dans mon cas d'utilisation, je n'avais pas besoin de creuser un tunnel jusqu'à la racine, évitant ainsi quelques événements de déclenchement et peut-être quelques précieuses nanosecondes de timeLes composants VueJS 2 ont une
$parent
propriété qui contient leur composant parent.Ce composant parent comprend également sa propre
$parent
propriété.Ensuite, pour accéder au composant "grand-parent", il s'agit d'accéder au composant "parent du parent":
this.$parent["$parent"].$emit("myevent", { data: 123 });
Quoi qu'il en soit, c'est un peu délicat , et je recommande d'utiliser un gestionnaire d'état global comme Vuex ou des outils similaires, comme l'ont dit d'autres intervenants.
la source
En écartant les réponses de @kubaklam et @ digout, voici ce que j'utilise pour éviter d'émettre sur chaque composant parent entre le petit-enfant et le grand-parent (éventuellement éloigné):
{ 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) } } }
Lors de la création d'un composant avec des petits-enfants éloignés où vous ne voulez pas que beaucoup / aucun composant soit lié au magasin, mais que vous voulez que le composant racine agisse comme un magasin / source de vérité, cela fonctionne très bien. Ceci est similaire à la philosophie des actions vers le bas des données d'Ember. L'inconvénient est que si vous voulez écouter cet événement sur chaque parent entre les deux, cela ne fonctionnera pas. Mais alors vous pouvez utiliser $ propogateEmit comme dans la réponse ci-dessus par @kubaklam.
Edit: la vm initiale doit être définie sur le composant et non sur le parent du composant. Ie
let vm = this
et paslet vm = this.$parent
la source
Je creuse vraiment la façon dont cela est géré en créant une classe liée à la fenêtre et en simplifiant la configuration de diffusion / écoute pour qu'elle fonctionne où que vous soyez dans l'application 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); } }
Maintenant, vous pouvez simplement tirer / diffuser / n'importe quoi de n'importe où en appelant:
Event.fire('do-the-thing');
... et vous pouvez écouter chez un parent, grand-parent, tout ce que vous voulez en appelant:
Event.listen('do-the-thing', () => { alert('Doing the thing!'); });
la source