VueJs 2.0, büyük çocuktan büyük ebeveyn bileşenine olay yayıyor


98

Görünüşe göre Vue.js 2.0, olayları büyük bir çocuktan büyük ebeveyn bileşenine yaymıyor.

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'
})

Bu JsFiddle, https://jsfiddle.net/y5dvkqbd/4/ sorununu çözer , ancak iki olay çıkararak:

  • Büyük çocuktan orta bileşene bir
  • Sonra orta bileşenden büyük ebeveyne yayılıyor

Bu orta olayı eklemek, tekrarlayan ve gereksiz görünüyor. Doğrudan büyük ebeveyne yaymanın farkında olmadığım bir yolu var mı?

Yanıtlar:


73

Vue 2.4, olayları hiyerarşiden kolayca geçirmenin bir yolunu tanıttı. vm.$listeners

Gönderen https://vuejs.org/v2/api/#vm-listeners :

Üst kapsam v-onolay dinleyicileri içerir ( .nativedeğiştiriciler olmadan ). Bu, aracılığıyla bir iç bileşene aktarılabilir v-on="$listeners"- şeffaf sarıcı bileşenleri oluştururken yararlıdır.

Kullanarak aşağıdaki pasajı bakın v-on="$listeners"içinde grand-childbileşene childşablonuna:

Vue.component('parent', {
  template:
    '<div>' +
      '<p>I am the parent. The value is {{displayValue}}.</p>' +
      '<child @toggle-value="toggleValue"></child>' +
    '</div>',
  data() {
    return {
      value: false
    }
  },
  methods: {
    toggleValue() { this.value = !this.value }
  },
  computed: {
    displayValue() {
      return (this.value ? "ON" : "OFF")
    }
  }
})

Vue.component('child', {
  template:
    '<div class="child">' +
      '<p>I am the child. I\'m just a wrapper providing some UI.</p>' +
      '<grand-child v-on="$listeners"></grand-child>' +
    '</div>'
})

Vue.component('grand-child', {
  template:
    '<div class="child">' +
      '<p>I am the grand-child: ' +
        '<button @click="emitToggleEvent">Toggle the value</button>' +
      '</p>' +
    '</div>',
  methods: {
    emitToggleEvent() { this.$emit('toggle-value') }
  }
})

new Vue({
  el: '#app'
})
.child {
  padding: 10px;
  border: 1px solid #ddd;
  background: #f0f0f0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <parent></parent>
</div>


42

Vue topluluğu genellikle bu tür sorunları çözmek için Vuex'i kullanmayı tercih eder. Vuex durumunda değişiklikler yapılır ve DOM temsili bundan kaynaklanır ve birçok durumda olay ihtiyacını ortadan kaldırır.

Bunun dışında, yeniden yayma muhtemelen bir sonraki en iyi seçenek olacaktır ve son olarak, bu soruya verilen diğer yüksek oylu yanıtta ayrıntılı olarak açıklanan bir olay veri yolunu kullanmayı seçebilirsiniz.

Aşağıdaki cevap, bu soruya verdiğim orijinal cevabım ve Vue ile daha fazla deneyime sahip olduğum için şimdi alacağım bir yaklaşım değil.


Bu, Vue'nun tasarım seçimine katılmayabileceğim ve DOM'a başvurabileceğim bir durum.

İçinde 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);
        }
    }
}

ve de parent,

mounted(){
    this.$el.addEventListener("eventtriggered", () => this.performAction())
}

Aksi takdirde, evet, yeniden yaymanız veya bir otobüs kullanmanız gerekir.

Not: IE'yi işlemek için doEvent yöntemine kod ekledim; bu kod yeniden kullanılabilir bir şekilde çıkarılabilir.


Bu, IE için farklı mı davranıyor? vue ile tarayıcı tutarsızlıkları olduğunun farkında değildim ...
BassMHL

@BassemLhm Vue, IE ile iyi. IE ile ilgili sorun Vue değil, bu bir DOM çözümüdür ve IE'de new Event () yapamazsınız. Document.createEvent () yapmanız gerekir. Gerekirse IE desteğini ekleyebilirim.
Bert

Vuex'i sadece basit bir durum için kurmak mantıklı değil.
Adam Orlov

@AdamOrlov Size katılıyorum.
Bert

Daha basit bir çözüm: stackoverflow.com/a/55650245/841591
digout

34

YENİ CEVAP (Kasım-2018 güncellemesi)

Bunu $parent, grand child bileşenindeki mülkten yararlanarak gerçekten yapabileceğimizi keşfettim :

this.$parent.$emit("submit", {somekey: somevalue})

Çok daha temiz ve daha basit.


21
Bunun yalnızca ilişki çocuk -> büyükbaba ise işe yarayacağını unutmayın. Bu mu değil çocuk derin iç içe keyfi seviyelerini olamayacağını çalışır.
Qtax

@Qtax'tan gelen yoruma yanıt olarak stackoverflow.com/a/55650245/841591 cevabımı görün
digout

9
Büyük projenizde bu tür şeylerin olmasını istemezsiniz. 'Çocuğu' transitionya da başka bir ambalaj bileşeninin içine koyarsınız ve kırılır ve kafanızda büyük bir soru işareti bırakır.
Adam Orlov

1
@AdamOrlov Katılıyorum, bu kötü bir uygulama. Lütfen bir Vuex mağazası kullanarak bunun gibi olayları yönetin.
Fabian von Ellerts


26

Evet, haklısınız olaylar sadece çocuktan ebeveyne gidiyor. Daha ileri gitmezler, örneğin çocuktan büyükbabaya.

Vue belgeleri (kısaca) bu durumu Ebeveyn-Çocuk İletişimi bölümünde ele almaktadır.

Genel fikir, büyükbaba veya büyükanne bileşeninde Vue, sahne aracılığıyla büyük ebeveynden çocuklara ve torunlara aktarılan boş bir bileşen oluşturmanızdır . Büyükbaba daha sonra olayları dinler ve torunlar olayları o "olay veri yolunda" yayınlar.

Bazı uygulamalar bileşen başına olay veriyolu yerine global bir olay veri yolu kullanır. Global bir olay veri yolu kullanmak, olayların farklı bileşenler arasında çakışmaması için benzersiz olay adlarına veya ad alanlarına sahip olmanız gerektiği anlamına gelir.

İşte basit bir global olay veri yolunun nasıl uygulanacağına dair bir örnek .


18

Başka bir çözüm, kök düğümde açık / yayılacaktır :

Kullanımları vm.$root.$emitiçinde grand-çocuk , daha sonra kullandığı vm.$root.$onatası (veya herhangi bir yerde istediğiniz).

Güncellenmiş : Bazen bazı özel durumlarda dinleyiciyi devre dışı bırakmak istersiniz, vm. $ Off kullanın (örneğin: vm.$root.off('event-name')yaşam döngüsü kancası = 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>


17

Esnek olmak ve basitçe tüm ebeveynlere ve ebeveynlerine köküne kadar özyinelemeli olarak bir etkinlik yayınlamak istiyorsanız, şöyle bir şey yapabilirsiniz:

let vm = this.$parent

while(vm) {
    vm.$emit('submit')
    vm = vm.$parent
}

4

Olay veriyolu kullandığım tek durum bu !! Derin iç içe geçmiş çocuktan doğrudan üst öğeye değil, iletişim için veri iletmek için

İlk olarak : Bu içerikle bir js dosyası oluşturun (ben bunu eventbus.js olarak adlandırıyorum):

import Vue from 'vue'    
Vue.prototype.$event = new Vue()

İkincisi : Alt bileşeninizde bir olay yayınlayın:

this.$event.$emit('event_name', 'data to pass')

Üçüncüsü : Ebeveynde o olayı dinleyin:

this.$event.$on('event_name', (data) => {
  console.log(data)
})

Not: Bu etkinliği artık istemiyorsanız lütfen kaydını iptal edin:

this.$event.$off('event_name')

BİLGİ: Aşağıdaki kişisel görüşü okumanıza gerek yok

Büyük çocuktan büyük ebeveyne iletişim için vuex kullanmayı sevmiyorum (veya benzer iletişim seviyesi).

Verileri büyük ebeveynden büyük çocuğa geçirmek için vue.js'de sağlama / enjeksiyon kullanabilirsiniz . Ancak tersi için benzer bir şey yok. (büyük çocuktan büyükbabaya) Bu yüzden bu tür bir iletişim yapmam gerektiğinde olay veri yolunu kullanıyorum.


2

@Digout cevabına göre kısa bir mixin yaptım. Projede global olarak kullanmak için Vue örneğinizin başlatılmasından (yeni Vue ...) önce koymak istiyorsunuz. Normal olaya benzer şekilde kullanabilirsiniz.

Vue.mixin({
  methods: {
    $propagatedEmit: function (event, payload) {
      let vm = this.$parent;
      while (vm) {
        vm.$emit(event, payload);
        vm = vm.$parent;
      }
    }
  }
})

bu çözüm, uygulamam için kullandığım şeydi, ancak targetRefhedeflediğiniz bileşen üzerindeki yayılmayı durduran ek bir parametre ekledim . whileKoşul şunlardır sonra olur && vm.$refs[targetRef]- Ayrıca eklemek gerekiyordu refhedeflenen bileşende niteliği , ben tünele köküne tüm yol gerek yoktu benim kullanım durumunda ateş birkaç olay ve belki değerli nanosaniye bir çift tasarruf zaman
mcgraw

2

VueJS 2 bileşenleri $parent, üst bileşenlerini içeren bir özelliğe sahiptir.

Bu ana bileşen aynı zamanda kendi $parentmülkünü de içerir .

Daha sonra, "büyük ebeveyn" bileşenine erişmek, "ebeveynin ebeveyn" bileşenine erişim meselesidir:

this.$parent["$parent"].$emit("myevent", { data: 123 });

Her neyse, bu biraz zor ve diğer yanıt verenlerin söylediği gibi, Vuex veya benzeri araçlar gibi küresel bir devlet yöneticisi kullanmanızı tavsiye ederim.


1

@ Kubaklam ve @ digout'un yanıtlarını inceleyerek, torun ile (muhtemelen uzaktaki) büyük ebeveyn arasındaki her ebeveyn bileşenine yayılmasını önlemek için kullandığım şey bu:

{
  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)
    }
  }
}

Çok sayıda / herhangi bir bileşenin mağazaya bağlanmasını istemediğiniz, ancak kök bileşenin bir depo / hakikat kaynağı olarak hareket etmesini istediğiniz, uzaktaki büyük çocuklarla bir bileşen oluştururken, bu oldukça iyi çalışır. Bu, Ember felsefesinin veri aşağı hareketlerine benzer. Dezavantajı, aradaki her ebeveynde bu olayı dinlemek istiyorsanız, o zaman bu işe yaramayacaktır. Ama sonra, @kubaklam tarafından verilen yukarıdaki cevapta olduğu gibi $ propogateEmit'i kullanabilirsiniz.

Düzenleme: ilk vm, bileşenin üst öğesi değil bileşene ayarlanmalıdır. Yani let vm = thisve değillet vm = this.$parent


0

Pencereye bağlı bir sınıf oluşturarak ve yayın / dinleme kurulumunu Vue uygulamasında nerede olursanız olun çalışması için basitleştirerek bunun nasıl işlendiğini gerçekten kazıyorum.

window.Event = new class {

    constructor() {
        this.vue = new Vue();
    }

    fire(event, data = null) {
        this.vue.$emit(event, data);
    }

    listen() {
        this.vue.$on(event, callback);  
    }

}

Artık arayarak herhangi bir yerden ateş edebilir / yayınlayabilir / her şeyi yapabilirsiniz:

Event.fire('do-the-thing');

... ve bir ebeveyni, büyükanne veya büyükbabayı, istediğiniz her şeyi arayarak dinleyebilirsiniz:

Event.listen('do-the-thing', () => {
    alert('Doing the thing!');
});

1
Son derece tavsiye ederim değil mevcut 3. parti kütüphaneleri ile mevcut özelliklerini veya çatışmayı üzerine yazmak çok kolay olduğundan, pencere nesnesine rastgele özelliklerini ekleyerek. Bunun yerine, bu sorunu çözmek için Vue kullanan herhangi biri bunun yerine @roli roli'nin cevabını kullanmalıdır
HMilbradt

1
Bu endişeyi tamamen anladığımdan veya kabul ettiğimden emin değilim. Prototipe bağlanmak iyi bir yaklaşımdır, ancak pencereye bağlanmak tıpkı daha fazla olmasa da yaygın ve muhtemelen bunu halletmenin daha standart bir yoludur. Özelliği adlandırırsınız, bu nedenle ad çakışmalarını önlemek kolaydır. medium.com/@amitavroy7/… stackoverflow.com/questions/15008464/… Bu aynı zamanda Jeff Way'in Laracasts'te kullandığı önerilen çözümdür. laracasts.com/series/learn-vue-2-step-by-step/episodes/13
fylzero
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.