Parece que Vue.js 2.0 no emite eventos de un nieto a su componente principal.
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'
})
Este JsFiddle resuelve el problema https://jsfiddle.net/y5dvkqbd/4/ , pero al generar dos eventos:
- Uno del nieto al componente medio
- Luego emitiendo nuevamente desde el componente medio al abuelo
Agregar este evento intermedio parece repetitivo e innecesario. ¿Hay alguna forma de emitir directamente al abuelo que no conozco?
fuente
La comunidad de Vue generalmente favorece el uso de Vuex para resolver este tipo de problema. Se realizan cambios en el estado de Vuex y la representación DOM simplemente fluye de eso, eliminando la necesidad de eventos en muchos casos.
Salvo eso, la reemisión sería probablemente la siguiente mejor opción y, por último , podría optar por utilizar un bus de eventos como se detalla en la otra respuesta altamente votada a esta pregunta.
La respuesta a continuación es mi respuesta original a esta pregunta y no es un enfoque que tomaría ahora, teniendo más experiencia con Vue.
Este es un caso en el que podría no estar de acuerdo con la elección de diseño de Vue y recurrir a DOM.
en
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); } } }
y
parent
,mounted(){ this.$el.addEventListener("eventtriggered", () => this.performAction()) }
De lo contrario, sí, tienes que volver a emitir, o utilizar un autobús.
Nota: agregué código en el método doEvent para manejar IE; ese código podría extraerse de forma reutilizable.
fuente
NUEVA RESPUESTA (actualización de noviembre de 2018)
Descubrí que en realidad podríamos hacer esto aprovechando la
$parent
propiedad en el componente de nietos:this.$parent.$emit("submit", {somekey: somevalue})
Mucho más limpio y sencillo.
fuente
transition
o cualquier otro componente de envoltura y se romperá, dejándote con un gran signo de interrogación en tu cabeza.Sí, tienes razón, los eventos solo van de un niño a otro. No van más allá, por ejemplo, de niño a abuelo.
La documentación de Vue (brevemente) aborda esta situación en la Comunicación sin padres e hijos sección .
La idea general es que en el componente abuelo crea un vacío
Vue
componente que se pasa de los abuelos a los hijos y nietos a través de accesorios. El abuelo luego escucha los eventos y los nietos emiten eventos en ese "bus de eventos".Algunas aplicaciones utilizan un bus de eventos global en lugar de un bus de eventos por componente. El uso de un bus de eventos global significa que deberá tener nombres de eventos únicos o espacios de nombres para que los eventos no entren en conflicto entre diferentes componentes.
A continuación, se muestra un ejemplo de cómo implementar un bus de eventos global simple .
fuente
Otra solución estará encendida / emitida en el nodo raíz :
Se usa
vm.$root.$emit
en nieto , luego se usavm.$root.$on
en el antepasado (o en cualquier lugar que desee).Actualizado : a veces le gustaría deshabilitar el oyente en algunas situaciones específicas, use vm. $ Off (por ejemplo:
vm.$root.off('event-name')
dentro del ciclo de vida 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>
fuente
Si desea ser flexible y simplemente transmitir un evento a todos los padres y a sus padres de forma recursiva hasta la raíz, puede hacer algo como:
let vm = this.$parent while(vm) { vm.$emit('submit') vm = vm.$parent }
fuente
Este es el único caso cuando uso bus de eventos ! Para pasar datos de la comunicación secundaria anidada profunda, a la comunicación no directamente parental.
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) })
Nota: Si ya no desea ese evento, anule el registro:
this.$event.$off('event_name')
No me gusta usar vuex para la comunicación entre nietos y abuelos (o un nivel de comunicación similar).
fuente
Hice un breve mixin basado en la respuesta de @digout. Quiere ponerlo, antes de la inicialización de su instancia de Vue (nuevo Vue ...) para usarlo globalmente en el proyecto. Puede usarlo de manera similar a un evento normal.
Vue.mixin({ methods: { $propagatedEmit: function (event, payload) { let vm = this.$parent; while (vm) { vm.$emit(event, payload); vm = vm.$parent; } } } })
fuente
targetRef
que detiene la propagación en el componente al que se dirige. Lawhile
condición incluiría entonces&& vm.$refs[targetRef]
: también necesitaría incluir eseref
atributo en el componente de destino.En mi caso de uso, no necesitaba hacer un túnel hasta la raíz, lo que evitaba que se dispararan algunos eventos y tal vez un par de preciosos nanosegundos de timeLos componentes de VueJS 2 tienen una
$parent
propiedad que contiene su componente principal.Ese componente principal también incluye su propia
$parent
propiedad.Luego, acceder al componente "abuelo" es una cuestión de acceder al componente "padre del padre":
this.$parent["$parent"].$emit("myevent", { data: 123 });
De todos modos, esto es un poco complicado , y recomiendo usar un administrador de estado global como Vuex o herramientas similares, como han dicho otros respondedores.
fuente
Haciendo caso omiso de las respuestas de @kubaklam y @ digout, esto es lo que uso para evitar emitir en cada componente principal entre el nieto y el abuelo (posiblemente distante):
{ 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) } } }
Cuando se crea un componente con nietos lejanos en el que no desea que muchos / ningún componente esté vinculado a la tienda, pero desea que el componente raíz actúe como un almacén / fuente de verdad, esto funciona bastante bien. Esto es similar a la filosofía de acciones de descarga de datos de Ember. La desventaja es que si desea escuchar ese evento en todos los padres intermedios, esto no funcionará. Pero luego puede usar $ propogateEmit como en la respuesta anterior de @kubaklam.
Editar: la máquina virtual inicial debe establecerse en el componente y no en el padre del componente. Es decir
let vm = this
y nolet vm = this.$parent
fuente
Realmente me encanta la forma en que esto se maneja creando una clase que está vinculada a la ventana y simplificando la configuración de transmisión / escucha para que funcione donde sea que se encuentre en la aplicación 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); } }
Ahora puede disparar / transmitir / lo que sea desde cualquier lugar llamando a:
Event.fire('do-the-thing');
... y puedes escuchar a un padre, abuelo, lo que quieras llamando a:
Event.listen('do-the-thing', () => { alert('Doing the thing!'); });
fuente