Parece que o Vue.js 2.0 não emite eventos de um neto para o componente de seu avô.
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 resolve o problema https://jsfiddle.net/y5dvkqbd/4/ , mas emtting dois eventos:
- Um do neto ao componente do meio
- Em seguida, emitindo novamente do componente do meio para o avô
Adicionar este evento intermediário parece repetitivo e desnecessário. Existe uma maneira de emitir diretamente para os avós que eu não conheço?
fonte
A comunidade Vue geralmente prefere usar Vuex para resolver esse tipo de problema. As alterações são feitas no estado Vuex e a representação DOM flui apenas a partir dele, eliminando a necessidade de eventos em muitos casos.
Exceto isso, reemitir seria provavelmente a próxima melhor escolha e, por último, você pode escolher usar um barramento de evento conforme detalhado na outra resposta altamente votada a esta pergunta.
A resposta abaixo é minha resposta original a esta pergunta e não é uma abordagem que eu faria agora, tendo mais experiência com Vue.
Este é um caso em que posso discordar da escolha de design do Vue e recorrer ao DOM.
Em
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); } } }
e em
parent
,mounted(){ this.$el.addEventListener("eventtriggered", () => this.performAction()) }
Caso contrário, sim, você tem que reemitir, ou usar um ônibus.
Observação: adicionei código no método doEvent para lidar com o IE; esse código pode ser extraído de forma reutilizável.
fonte
NOVA RESPOSTA (atualização de novembro de 2018)
Descobri que poderíamos realmente fazer isso aproveitando a
$parent
propriedade no componente neto:this.$parent.$emit("submit", {somekey: somevalue})
Muito mais limpo e simples.
fonte
transition
ou em qualquer outro componente do invólucro e ele se quebra, deixando você com um grande ponto de interrogação em sua cabeça.Sim, seus eventos corretos só vão de filho para pai. Eles não vão além, por exemplo, da criança ao avô.
A documentação do Vue (resumidamente) aborda essa situação na seção Comunicação entre pais e filhos .
A ideia geral é que no componente avô você crie um
Vue
componente vazio que é passado do avô aos filhos e netos por meio de acessórios. O avô então escuta os eventos e os netos emitem eventos nesse "barramento de eventos".Alguns aplicativos usam um barramento de evento global em vez de um barramento de evento por componente. Usar um barramento de evento global significa que você precisará ter nomes de eventos exclusivos ou namespacing para que os eventos não entrem em conflito entre os diferentes componentes.
Aqui está um exemplo de como implementar um barramento de evento global simples .
fonte
Outra solução será ligar / emitir no nó raiz :
Usa
vm.$root.$emit
no neto e depoisvm.$root.$on
no ancestral (ou em qualquer lugar que você quiser).Atualizado : às vezes, você gostaria de desativar o ouvinte em algumas situações específicas, use vm. $ Off (por exemplo:
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>
fonte
Se você deseja ser flexível e simplesmente transmitir um evento para todos os pais e seus pais recursivamente até a raiz, você pode fazer algo como:
let vm = this.$parent while(vm) { vm.$emit('submit') vm = vm.$parent }
fonte
Este é o único caso quando eu uso o bus de eventos !! Para passar dados do filho aninhado profundo, para o pai não diretamente, comunicação.
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) })
Observação: se você não quiser mais esse evento, cancele o registro:
this.$event.$off('event_name')
Não gosto de usar vuex para comunicação de avô para avô (ou nível de comunicação semelhante).
fonte
Fiz um pequeno mixin baseado na resposta @digout. Você quer colocá-lo, antes da inicialização da instância do Vue (novo Vue ...) para usá-lo globalmente no projeto. Você pode usá-lo de forma semelhante ao evento normal.
Vue.mixin({ methods: { $propagatedEmit: function (event, payload) { let vm = this.$parent; while (vm) { vm.$emit(event, payload); vm = vm.$parent; } } } })
fonte
targetRef
que interrompe a propagação no componente que você está almejando. Awhile
condição, então, incluiria&& vm.$refs[targetRef]
- você também precisaria incluir esseref
atributo no componente de destino. Em meu caso de uso, eu não precisei fazer um túnel até o root, evitando que alguns eventos fossem disparados e talvez alguns preciosos nanossegundos de horaOs componentes VueJS 2 possuem uma
$parent
propriedade que contém seu componente pai.Esse componente pai também inclui sua própria
$parent
propriedade.Então, para acessar o componente "avô" é uma questão de acessar o componente "pai dos pais":
this.$parent["$parent"].$emit("myevent", { data: 123 });
De qualquer forma, isso é meio complicado , e eu recomendo usar um gerenciador de estado global como o Vuex ou ferramentas semelhantes, como outros respondentes disseram.
fonte
Analisando as respostas de @kubaklam e @digout, isto é o que eu uso para evitar emitir em todos os componentes dos pais entre o neto e o avô (possivelmente 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) } } }
Ao construir um componente com netos distantes onde você não deseja que muitos / nenhum componente seja vinculado ao armazenamento, mas deseja que o componente raiz atue como um armazenamento / fonte da verdade, isso funciona muito bem. Isso é semelhante à filosofia de ações para baixo de dados para cima do Ember. O lado negativo é que, se você quiser ouvir esse evento em todos os pais entre eles, isso não funcionará. Mas então você pode usar $ propogateEmit como na resposta acima por @kubaklam.
Editar: a vm inicial deve ser definida para o componente, e não para o pai do componente. Ou seja
let vm = this
e nãolet vm = this.$parent
fonte
Eu realmente gosto de como isso é tratado, criando uma classe vinculada à janela e simplificando a configuração de transmissão / escuta para funcionar onde quer que você esteja no aplicativo 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); } }
Agora você pode simplesmente disparar / transmitir / qualquer coisa de qualquer lugar chamando:
Event.fire('do-the-thing');
... e você pode ouvir um pai, avô, o que quiser, chamando:
Event.listen('do-the-thing', () => { alert('Doing the thing!'); });
fonte