VueJs 2.0'da kardeş bileşenler arasındaki iletişim


113

genel bakış

Vue.js 2.x model.syncedilecektir kaldırılmış .

Peki, Vue.js 2.x'te kardeş bileşenler arasında iletişim kurmanın uygun bir yolu nedir?


Arka fon

Vue 2.x'i anladığım kadarıyla, kardeş iletişimi için tercih edilen yöntem bir mağaza veya bir olay veriyolu kullanmaktır .

Göre Evan (Vue yaratıcısı):

Ayrıca, "verilerin bileşenler arasında geçirilmesi" genel olarak kötü bir fikirdir, çünkü sonunda veri akışı izlenemez ve hata ayıklaması çok zor hale gelir.

Bir veri parçasının birden fazla bileşen tarafından paylaşılması gerekiyorsa, global mağazaları veya Vuex'i tercih edin .

[ Tartışmaya bağlantı ]

Ve:

.onceve .synckullanımdan kaldırıldı. Aksesuarlar artık her zaman tek yönlüdür. Üst kapsamda yan etkiler üretmek için, bir bileşenin emitörtük bağlamaya dayanmak yerine açıkça bir olay olması gerekir .

Evan ,$emit() ve kullanmayı öneriyor$on() .


Endişeler

Beni endişelendiren şu:

  • Her storeve eventküresel görünürlüğü (yanılıyorsam düzeltin) sahiptir;
  • Her küçük iletişim için yeni bir mağaza oluşturmak çok israftır;

İstediğim şey, kardeş bileşenler için bir kapsam events veya storesgörünürlük. (Ya da belki yukarıdaki fikri anlamadım.)


Soru

Öyleyse, kardeş bileşenler arasında iletişim kurmanın doğru yolu nedir?


2
$emitv-modeltaklit etmek için ile birlikte .sync. Bence Vuex yoluna
gitmelisin

3
Ben de aynı endişeyi düşündüm. Çözümüm, "kapsam" a eşdeğer bir yayın kanalına sahip bir olay yayıcı kullanmaktır - yani, bir çocuk / ebeveyn ve kardeş kurulumu iletişim için aynı kanalı kullanır. Benim durumumda, radio.uxder.com radyo kitaplığını kullanıyorum çünkü sadece birkaç satırlık bir kod ve kurşun geçirmez, ancak çoğu EventEmitter düğümünü seçiyor.
Tremendus Uygulamaları

Yanıtlar:


84

Vue 2.0 ile, belgelerde gösterildiği gibi eventHub mekanizmasını kullanıyorum .

  1. Merkezi olay merkezini tanımlayın.

    const eventHub = new Vue() // Single event hub
    
    // Distribute to components using global mixin
    Vue.mixin({
        data: function () {
            return {
                eventHub: eventHub
            }
        }
    })
    
  2. Artık bileşeninizde ile olayları yayınlayabilirsiniz.

    this.eventHub.$emit('update', data)
  3. Ve dinlemek için yaparsın

    this.eventHub.$on('update', data => {
    // do your thing
    })
    

Güncelleme Lütfen daha basit bir çözümü açıklayan @alex yanıtına bakın .


3
Bir uyarı : Global Mixin'lere bir göz atın ve bu bağlantıya göre vuejs.org/v2/guide/mixins.html#Global-Mixin üçüncü şahıs bileşenlerini bile etkileyebileceğinden , mümkün olduğunca bunlardan kaçınmaya çalışın .
Vini.g.fer

6
Çok daha basit bir çözüm, @Alex'in tanımladığı şeyi kullanmaktır - this.$root.$emit()vethis.$root.$on()
Webnet

5
İleride başvurmak için lütfen yanıtınızı başka birinin cevabıyla güncellemeyin (daha iyi olduğunu düşünseniz ve ona referans verseniz bile). Alternatif yanıta bağlantı verin veya hatta OP'den, eğer yapmaları gerektiğini düşünüyorsanız diğerini kabul etmesini isteyin. sadece cevap. Onları, referans verdiğiniz yanıta gitmeleri (ve böylece, bu yanıtı kendinize eklemeyerek) yükseltmeleri için teşvik edin.
GrayedFox

4
Değerli geri bildiriminiz için teşekkür ederim @GrayedFox, cevabımı buna göre güncelledi.
kakoni

2
Lütfen bu çözümün artık Vue 3'te desteklenmeyeceğini unutmayın. Stackoverflow.com/a/60895076/752916
AlexMA

146

Hatta kısaltabilir ve kök Vue örneğini global Event Hub olarak kullanabilirsiniz :

Bileşen 1:

this.$root.$emit('eventing', data);

Bileşen 2:

mounted() {
    this.$root.$on('eventing', data => {
        console.log(data);
    });
}

2
Bu, ek bir olay hub'ı tanımlamaktan ve bunu herhangi bir olay tüketicisine eklemekten daha iyi çalışır.
schad

2
Kapsamlı olayları gerçekten sevmediğim için bu çözümün büyük bir hayranıyım. Ancak, her gün VueJS ile çalışmıyorum, bu yüzden bu yaklaşımla ilgili sorunları gören biri olup olmadığını merak ediyorum.
Webnet

2
Tüm cevapların en basit çözümü
Vikash Gupta

1
güzel, kısa ve uygulaması kolay, anlaşılması da kolay
nada

1
Yalnızca doğrudan kardeşlerle iletişim
kurmak istiyorsanız

47

İletişim türleri

Bir Vue uygulaması (veya aslında herhangi bir bileşen tabanlı uygulama) tasarlarken, hangi endişelerle uğraştığımıza bağlı olarak farklı iletişim türleri vardır ve bunların kendi iletişim kanalları vardır.

İş mantığı: Uygulamanıza ve amacına özgü her şeyi ifade eder.

Sunum mantığı: Kullanıcının etkileşim kurduğu veya kullanıcının etkileşiminden kaynaklanan her şey.

Bu iki endişe, bu tür iletişimlerle ilgilidir:

  • Uygulama durumu
  • Ebeveyn-çocuk
  • Çocuk ebeveyn
  • Kardeşler

Her tür doğru iletişim kanalını kullanmalıdır.


İletişim kanalları

Kanal, bir Vue uygulaması etrafında veri alışverişi yapmak için somut uygulamalara atıfta bulunmak için kullanacağım gevşek bir terimdir.

Destekler: Ebeveyn-Çocuk sunum mantığı

Doğrudan Ebeveyn-Çocuk iletişimi için Vue'daki en basit iletişim kanalı . Çoğunlukla sunum mantığıyla veya sınırlı bir veri kümesiyle ilgili verileri hiyerarşinin altına aktarmak için kullanılmalıdır.

Referanslar ve yöntemler: Sunum anti-model

Bir çocuğun bir ebeveynden gelen bir olayı işlemesine izin vermek için bir pervane kullanmak mantıklı olmadığında , alt bileşen üzerinde bir reföğe oluşturmak ve onun yöntemlerini çağırmak gayet iyi.

Bunu yapma, bu bir anti-modeldir. Bileşen mimarinizi ve veri akışınızı yeniden düşünün. Kendinizi bir ebeveynden bir alt bileşende bir yöntem çağırmak isterken bulursanız, muhtemelen durumu yükseltmenin veya burada veya diğer cevaplarda açıklanan diğer yolları düşünmenin zamanı gelmiştir .

Olaylar: Alt-Üst sunum mantığı

$emitve $on. Doğrudan Çocuk-Ebeveyn iletişimi için en basit iletişim kanalı. Yine sunum mantığı için kullanılmalıdır.

Etkinlik veriyolu

Yanıtların çoğu, uzak bileşenler veya aslında herhangi bir şey için mevcut iletişim kanallarından biri olan olay veri yolu için iyi alternatifler sunar.

Bu, sahne donanımlarını çok yukarıdan derinlemesine iç içe geçmiş alt bileşenlere geçirirken, aralarında neredeyse hiçbir bileşene ihtiyaç duymadan, yararlı olabilir. Dikkatlice seçilmiş veriler için idareli kullanın.

Dikkatli olun: Kendilerini olay veri yoluna bağlayan bileşenlerin daha sonra oluşturulması birden fazla kez bağlanacaktır - bu da birden çok işleyicinin tetiklenmesine ve sızıntılara neden olur. Geçmişte tasarladığım tüm tek sayfalık uygulamalarda kişisel olarak bir etkinlik otobüsüne ihtiyaç duymadım.

Aşağıdaki, basit bir hatanın, Itembileşenin DOM'dan kaldırılsa bile hala tetiklendiği bir sızıntıya nasıl yol açtığını göstermektedir .

destroyedYaşam döngüsü kancasından dinleyicileri çıkarmayı unutmayın .

Merkezi mağaza (İş mantığı)

Vuex , durum yönetimi için Vue ile gitmenin yoludur . Olaylardan çok daha fazlasını sunar ve tam ölçekli uygulama için hazırdır.

Ve şimdi soruyorsun :

[S] Her küçük iletişim için vuex'in deposunu oluşturmalı mıyım?

Gerçekten ne zaman parlıyor:

  • iş mantığınızla ilgilenmek,
  • bir arka uçla (veya yerel depolama gibi herhangi bir veri kalıcılığı katmanıyla) iletişim kurma

Böylece bileşenleriniz gerçekten olması gereken şeylere, yani kullanıcı arayüzlerini yönetmeye odaklanabilir.

Bu, onu bileşen mantığı için kullanamayacağınız anlamına gelmez, ancak bu mantığı yalnızca gerekli global UI durumuna sahip ad alanlı bir Vuex modülüne dahil edeceğim.

Küresel bir durumda her şeyin büyük bir karmaşasıyla uğraşmaktan kaçınmak için, mağaza birden çok ad alanlı modülde ayrılmalıdır.


Bileşen türleri

Tüm bu iletişimi düzenlemek ve yeniden kullanılabilirliği kolaylaştırmak için bileşenleri iki farklı tür olarak düşünmeliyiz.

  • Uygulamaya özel kapsayıcılar
  • Genel bileşenler

Yine, genel bir bileşenin yeniden kullanılması gerektiği veya uygulamaya özel bir kapsayıcının yeniden kullanılamayacağı anlamına gelmez, ancak farklı sorumlulukları vardır.

Uygulamaya özel kapsayıcılar

Bunlar, diğer Vue bileşenlerini (jenerik veya diğer uygulamaya özel kapsayıcılar) saran basit bir Vue bileşenidir. Bu, Vuex mağaza iletişiminin gerçekleşmesi gereken yerdir ve bu kapsayıcı, sahne donanımı ve olay dinleyicileri gibi diğer daha basit yollarla iletişim kurmalıdır.

Bu kapsayıcıların hiçbir yerel DOM öğesi bile olmayabilir ve genel bileşenlerin şablon oluşturma ve kullanıcı etkileşimleriyle ilgilenmesine izin verebilir.

kardeş bileşenleri için bir şekilde kapsamevents veya storesgörünürlük

Kapsam belirleme burada gerçekleşir. En bileşenleri mağaza hakkında bilgi yoktur ve bu bileşen (çoğunlukla) kullanılması, bir sınırlı bir dizi mağaza modülü alanlı olmalıdır gettersve actionsResim uygulanan Vuex bağlanma yardımcıları .

Genel bileşenler

Bunlar verilerini sahne donanımlarından almalı, kendi yerel verilerinde değişiklik yapmalı ve basit olaylar yaymalıdır. Çoğu zaman, bir Vuex mağazasının var olduğunu bilmemelidirler.

Tek sorumlulukları diğer UI bileşenlerine göndermek olabileceğinden bunlara konteyner de denilebilir.


Kardeş iletişimi

Öyleyse, tüm bunlardan sonra, iki kardeş bileşen arasında nasıl iletişim kurmalıyız?

Bir örnekle anlaşılması daha kolay: Bir giriş kutumuz olduğunu ve verilerinin uygulama genelinde paylaşılması (ağaçta farklı yerlerdeki kardeşler) ve bir arka uç ile kalıcı olması gerektiğini söyleyin.

En kötü durum senaryosundan başlayarak , bileşenimiz sunum ve mantığını karıştıracaktır .

// MyInput.vue
<template>
    <div class="my-input">
        <label>Data</label>
        <input type="text"
            :value="value" 
            :input="onChange($event.target.value)">
    </div>
</template>
<script>
    import axios from 'axios';

    export default {
        data() {
            return {
                value: "",
            };
        },
        mounted() {
            this.$root.$on('sync', data => {
                this.value = data.myServerValue;
            });
        },
        methods: {
            onChange(value) {
                this.value = value;
                axios.post('http://example.com/api/update', {
                        myServerValue: value
                    })
                    .then((response) => {
                        this.$root.$emit('update', response.data);
                    });
            }
        }
    }
</script>

Bu iki endişeyi ayırmak için, bileşenimizi uygulamaya özel bir kapsayıcıya sarmalı ve sunum mantığını genel girdi bileşenimizde tutmalıyız.

Girdi bileşenimiz artık yeniden kullanılabilir ve arka uç veya kardeşler hakkında bilgi sahibi değildir.

// MyInput.vue
// the template is the same as above
<script>
    export default {
        props: {
            initial: {
                type: String,
                default: ""
            }
        },
        data() {
            return {
                value: this.initial,
            };
        },
        methods: {
            onChange(value) {
                this.value = value;
                this.$emit('change', value);
            }
        }
    }
</script>

Uygulamaya özel kapsayıcımız artık iş mantığı ile sunum iletişimi arasında köprü olabilir.

// MyAppCard.vue
<template>
    <div class="container">
        <card-body>
            <my-input :initial="serverValue" @change="updateState"></my-input>
            <my-input :initial="otherValue" @change="updateState"></my-input>

        </card-body>
        <card-footer>
            <my-button :disabled="!serverValue || !otherValue"
                       @click="saveState"></my-button>
        </card-footer>
    </div>
</template>
<script>
    import { mapGetters, mapActions } from 'vuex';
    import { NS, ACTIONS, GETTERS } from '@/store/modules/api';
    import { MyButton, MyInput } from './components';

    export default {
        components: {
            MyInput,
            MyButton,
        },
        computed: mapGetters(NS, [
            GETTERS.serverValue,
            GETTERS.otherValue,
        ]),
        methods: mapActions(NS, [
            ACTIONS.updateState,
            ACTIONS.updateState,
        ])
    }
</script>

Vuex mağaza eylemleri arka uç iletişimiyle ilgilendiğinden , buradaki kapsayıcımızın aksiyolar ve arka uç hakkında bilgi sahibi olmasına gerek yoktur.


3
"Olmanın yöntemleri hakkında yorum ile katılıyorum o sahne kullanarak aynı kavrama "
ghybs

Bu cevabı beğendim. Ama lütfen Event Bus ve "Dikkatli olun:" notunu detaylandırır mısınız? Belki bir örnek verebilirsin, bileşenlerin nasıl iki kez bağlanabileceğini anlamıyorum.
vandroid

Üst bileşen ile ana bileşen arasında nasıl iletişim kurarsınız, örneğin form doğrulama. Üst bileşen bir sayfa, alt öğe form ve büyük çocuk, girdi formu öğesi olduğunda?
Lord Zed

1
@vandroid Bu konudaki her örnekte olduğu gibi, dinleyiciler düzgün bir şekilde kaldırılmadığında bir sızıntıyı gösteren basit bir örnek oluşturdum.
Emile Bergeron

@LordZed Gerçekten bağlıdır, ancak durumunuz hakkındaki anlayışıma göre, bir tasarım problemi gibi görünüyor. Vue, daha çok sunum mantığı için kullanılmalıdır. Form doğrulama, bir Vuex eyleminin formdaki verilerle çağıracağı vanilla JS API arabiriminde olduğu gibi başka bir yerde yapılmalıdır.
Emile Bergeron

10

Tamam, kardeşler arasında v-onolayları kullanarak ebeveyn aracılığıyla iletişim kurabiliriz .

Parent
 |-List of items //sibling 1 - "List"
 |-Details of selected item //sibling 2 - "Details"

DetailsBir elemente tıkladığımızda bileşeni güncellemek istediğimizi varsayalım List.


içinde Parent:

Şablon:

<list v-model="listModel"
      v-on:select-item="setSelectedItem" 
></list> 
<details v-model="selectedModel"></details>

Buraya:

  • v-on:select-itembu Listbileşende çağrılacak bir olaydır (aşağıya bakınız);
  • setSelectedItemParentgüncelleme için bir yöntemdir selectedModel;

JS:

//...
data () {
  return {
    listModel: ['a', 'b']
    selectedModel: null
  }
},
methods: {
  setSelectedItem (item) {
    this.selectedModel = item //here we change the Detail's model
  },
}
//...

İçinde List:

Şablon:

<ul>
  <li v-for="i in list" 
      :value="i"
      @click="select(i, $event)">
        <span v-text="i"></span>
  </li>
</ul>

JS:

//...
data () {
  return {
    selected: null
  }
},
props: {
  list: {
    type: Array,
    required: true
  }
},
methods: {
  select (item) {
    this.selected = item
    this.$emit('select-item', item) // here we call the event we waiting for in "Parent"
  },
}
//...

Buraya:

  • this.$emit('select-item', item)öğeyi select-itemdoğrudan ebeveyn üzerinden gönderecek . Ve ebeveyn onu Detailsgörünüme gönderecek

5

.syncVue'daki normal iletişim kalıplarını "kesmek" istersem genellikle yaptığım şey, özellikle artık kullanımdan kaldırıldı, bileşenler arasındaki iletişimi yöneten basit bir EventEmitter oluşturmaktır. En son projelerimden birinden:

import {EventEmitter} from 'events'

var Transmitter = Object.assign({}, EventEmitter.prototype, { /* ... */ })

Bu Transmitternesne ile herhangi bir bileşende şunları yapabilirsiniz:

import Transmitter from './Transmitter'

var ComponentOne = Vue.extend({
  methods: {
    transmit: Transmitter.emit('update')
  }
})

Ve bir "alıcı" bileşen oluşturmak için:

import Transmitter from './Transmitter'

var ComponentTwo = Vue.extend({
  ready: function () {
    Transmitter.on('update', this.doThingOnUpdate)
  }
})

Yine, bu gerçekten özel kullanımlar içindir. Tüm uygulamanızı bu modele dayandırmayın, Vuexbunun yerine benzer bir şey kullanın.


1
Zaten kullanıyorum vuex, ancak yine, her küçük iletişim için vuex'in deposunu oluşturmalı mıyım?
Sergei Panfilov

Bu kadar bilgiyle söylemek benim için zor, ama zaten vuexevet kullanıyorsanız , bunun için gidin derim . Kullan onu.
Hector Lorenzo

1
Aslında, her küçük iletişim için vuex kullanmamız gerektiğine katılmıyorum ...
Victor

Hayır, elbette hayır, hepsi içeriğe bağlı. Aslında cevabım vuex'ten uzaklaşıyor. Öte yandan, vuex'i ve merkezi durum nesnesi kavramını ne kadar çok kullanırsanız, nesneler arasındaki iletişime o kadar az güvendiğimi buldum. Ama evet, katılıyorum, her şey değişir.
Hector Lorenzo

3

Kardeşler arasındaki iletişimin nasıl yapılacağı duruma bağlıdır. Ama önce Vue 3'te küresel etkinlik otobüsü yaklaşımının ortadan kalktığını vurgulamak istiyorum . Bu RFC'ye bakın . Bu yüzden yeni bir cevap yazmaya karar verdim.

En Düşük Ortak Ata Modeli (veya "LCA")

Basit durumlar için, En Düşük Ortak Ata modelini kullanmanızı şiddetle tavsiye ederim ("veri azaldı, olaylar arttı" olarak da bilinir). Bu modelin okunması, uygulanması, test edilmesi ve hata ayıklaması kolaydır.

Temelde bu, iki bileşenin iletişim kurması gerekiyorsa, ortak durumlarını her ikisinin de ata olarak paylaştığı en yakın bileşene koyması anlamına gelir. Props aracılığıyla üst bileşenden alt bileşene veri aktarın ve bir olay yayarak bilgileri çocuktan üst öğeye aktarın (bunun örneğini bu cevabın altında görebilirsiniz).

Uydurulmuş bir örnek için, bir e-posta uygulamasında, "Kime" bileşeninin "mesaj gövdesi" bileşeniyle etkileşime girmesi gerekiyorsa, bu etkileşimin durumu ebeveynlerinde yaşayabilir (belki de adlandırılan bir bileşen email-form). Mesaj gövdesinin alıcının e-posta adresine göre e-postanın başına otomatik olarak eklenebilmesi için email-formçağrıda bir propunuz olabilir .addresseeDear {{addressee.name}}

İletişimin birçok aracı bileşenle uzun mesafeler kat etmesi gerekiyorsa, LCA külfetli hale gelir. Sık sık meslektaşlarımı bu mükemmel blog gönderisine yönlendiriyorum . (Örneklerinin Ember kullandığı gerçeğini göz ardı edin; fikirleri birçok UI çerçevesinde uygulanabilir.)

Veri Kabı Modeli (ör. Vuex)

Ebeveyn-çocuk iletişiminin çok fazla aracı içerdiği karmaşık durumlar veya durumlar için, Vuex veya eşdeğer bir veri taşıyıcı teknolojisi kullanın. Uygun olduğunda, ad alanlı modülleri kullanın .

Örneğin, tam özellikli bir takvim bileşeni gibi birçok ara bağlantıya sahip karmaşık bir bileşen koleksiyonu için ayrı bir ad alanı oluşturmak mantıklı olabilir.

Yayınlama / Abone Olma (Etkinlik Otobüsü) Kalıbı

Olay veriyolu (veya "yayınla / abone ol") kalıbı ihtiyaçlarınıza daha uygunsa, Vue çekirdek ekibi artık eldiven gibi bir üçüncü taraf kitaplığı kullanmanızı tavsiye ediyor . (1. paragrafta atıfta bulunulan RFC'ye bakın.)

Bonus saçmalıklar ve kod

İşte kardeşten kardeşe iletişim için En Düşük Ortak Ata çözümünün temel bir örneği, oyunda köstebek vurma oyunu ile resmedilmiştir .

Saf bir yaklaşım, "ben 1, köstebek 2'ye vurulduktan sonra görünmesini söylemeli" diye düşünmek olabilir. Ancak Vue, bizden ağaç yapıları açısından düşünmemizi istediği için bu tür bir yaklaşımı caydırıyor .

Bu muhtemelen çok iyi bir şey. Düğümlerin DOM ağaçları arasında doğrudan birbirleriyle iletişim kurduğu önemsiz olmayan bir uygulama, bir tür muhasebe sistemi olmadan (Vuex'in sağladığı gibi) hata ayıklamak çok zor olacaktır. Bunun da ötesinde, "veri aşağı, olaylar yukarı" kullanan bileşenler, düşük birleştirme ve yüksek yeniden kullanılabilirlik sergilemeye meyillidir - her ikisi de büyük uygulamaların ölçeklenmesine yardımcı olan oldukça istenen özelliklerdir.

Bu örnekte, bir köstebek vurulduğunda, bir olay yayar. Oyun yöneticisi bileşeni, uygulamanın yeni durumunun ne olduğuna karar verir ve böylece kardeş köstebek Vue yeniden oluşturduktan sonra örtük olarak ne yapacağını bilir. Bu biraz önemsiz bir "en düşük ortak ata" örneği.

Vue.component('whack-a-mole', {
  data() {
    return {
      stateOfMoles: [true, false, false],
      points: 0
    }
  },
  template: `<div>WHACK - A - MOLE!<br/>
    <a-mole :has-mole="stateOfMoles[0]" v-on:moleMashed="moleClicked(0)"/>
    <a-mole :has-mole="stateOfMoles[1]"  v-on:moleMashed="moleClicked(1)"/>
    <a-mole :has-mole="stateOfMoles[2]" v-on:moleMashed="moleClicked(2)"/>
    <p>Score: {{points}}</p>
</div>`,
  methods: {
    moleClicked(n) {
      if(this.stateOfMoles[n]) {
         this.points++;
         this.stateOfMoles[n] = false;
         this.stateOfMoles[Math.floor(Math.random() * 3)] = true;
      }   
    }
  }
})

Vue.component('a-mole', {
  props: ['hasMole'],
  template: `<button @click="$emit('moleMashed')">
      <span class="mole-button" v-if="hasMole">🐿</span><span class="mole-button" v-if="!hasMole">🕳</span>
    </button>`
})

var app = new Vue({
  el: '#app',
  data() {
    return { name: 'Vue' }
  }
})
.mole-button {
  font-size: 2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <whack-a-mole />
</div>

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.