Kommunikation zwischen Geschwisterkomponenten in VueJs 2.0

112

Überblick

In Vue.js 2.x model.syncwird veraltet .

Was ist also ein angemessener Weg, um zwischen Geschwisterkomponenten in Vue.js 2.x zu kommunizieren ?


Hintergrund

Nach meinem Verständnis von Vue 2.x ist die bevorzugte Methode für die Kommunikation zwischen Geschwistern die Verwendung eines Geschäfts oder eines Ereignisbusses .

Laut Evan (Schöpfer von Vue):

Erwähnenswert ist auch, dass "Daten zwischen Komponenten übertragen" im Allgemeinen eine schlechte Idee ist, da der Datenfluss am Ende nicht mehr nachvollziehbar und sehr schwer zu debuggen ist.

Wenn ein Datenelement von mehreren Komponenten gemeinsam genutzt werden muss, bevorzugen Sie globale Speicher oder Vuex .

[ Link zur Diskussion ]

Und:

.onceund .syncsind veraltet. Requisiten sind jetzt immer in eine Richtung. Um Nebenwirkungen im übergeordneten Bereich zu erzeugen, muss eine Komponente explizit emitein Ereignis sein, anstatt sich auf implizite Bindung zu verlassen.

Also schlägt Evan vor , $emit()und zu verwenden $on().


Sorgen

Was mich beunruhigt ist:

  • Jeder storeund eventhat eine globale Sichtbarkeit (korrigieren Sie mich, wenn ich falsch liege);
  • Es ist zu verschwenderisch, für jede kleinere Kommunikation ein neues Geschäft zu erstellen.

Was ich möchte, ist ein gewisser Umfang events oder eine gewisse storesSichtbarkeit für Geschwisterkomponenten. (Oder vielleicht habe ich die obige Idee nicht verstanden.)


Frage

Was ist also der richtige Weg, um zwischen Geschwisterkomponenten zu kommunizieren?

Sergei Panfilov
quelle
2
$emitkombiniert mit v-modelzu emulieren .sync. Ich denke, Sie sollten den Vuex-Weg gehen
eltonkamami
3
Also habe ich das gleiche Anliegen berücksichtigt. Meine Lösung besteht darin, einen Ereignisemitter mit einem Broadcast-Kanal zu verwenden, der dem Bereich entspricht. Das heißt, ein Kinder- / Eltern- und Geschwister-Setup verwenden denselben Kanal für die Kommunikation. In meinem Fall verwende ich die Radiobibliothek radio.uxder.com, da es sich nur um ein paar Codezeilen handelt und es kugelsicher ist, aber viele würden den Knoten EventEmitter wählen.
Tremendus Apps

Antworten:

83

Mit Vue 2.0 verwende ich den eventHub-Mechanismus, wie in der Dokumentation gezeigt .

  1. Definieren Sie einen zentralen Ereignis-Hub.

    const eventHub = new Vue() // Single event hub
    
    // Distribute to components using global mixin
    Vue.mixin({
        data: function () {
            return {
                eventHub: eventHub
            }
        }
    })
  2. Jetzt können Sie in Ihrer Komponente Ereignisse mit ausgeben

    this.eventHub.$emit('update', data)
  3. Und um zuzuhören, tust du es

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

Update Bitte lesen Sie die Antwort von @alex , die eine einfachere Lösung beschreibt.

Kakoni
quelle
3
Nur ein Hinweis: Behalten Sie Global Mixins im Auge und versuchen Sie, sie nach Möglichkeit zu vermeiden, da sie laut diesem Link vuejs.org/v2/guide/mixins.html#Global-Mixin sogar Komponenten von Drittanbietern betreffen können.
Vini.g.fer
6
Eine viel einfachere Lösung besteht darin, das zu verwenden, was @Alex beschrieben hat - this.$root.$emit()undthis.$root.$on()
Webnet
5
Bitte aktualisieren Sie Ihre Antwort zum späteren Nachschlagen nicht mit der Antwort eines anderen (auch wenn Sie der Meinung sind, dass dies besser ist und Sie darauf verweisen). Verknüpfen Sie die alternative Antwort oder bitten Sie das OP, die andere zu akzeptieren, wenn Sie der Meinung sind, dass dies der Fall sein sollte. Das Kopieren der Antwort in Ihre eigene ist jedoch eine schlechte Form und hält Benutzer davon ab, Gutschriften zu geben, wenn sie fällig sind, da sie möglicherweise nur Ihre Antwort positiv bewerten antworte nur. Ermutigen Sie sie, zu der Antwort zu navigieren (und sie damit zu bewerten), auf die Sie sich beziehen, indem Sie diese Antwort nicht in Ihre eigene aufnehmen.
GrayedFox
4
Vielen Dank für das wertvolle Feedback @GrayedFox, meine Antwort entsprechend aktualisiert.
Kakoni
2
Bitte beachten Sie, dass diese Lösung in Vue 3 nicht mehr unterstützt wird. Siehe stackoverflow.com/a/60895076/752916
AlexMA
145

Sie können es sogar kürzer machen und die Root- Vue Instanz als globalen Event Hub verwenden:

Komponente 1:

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

Komponente 2:

mounted() {
    this.$root.$on('eventing', data => {
        console.log(data);
    });
}
Alex
quelle
2
Dies funktioniert besser, als einen zusätzlichen Event-Hub zu definieren und ihn an einen Event-Consumer anzuhängen.
schad
2
Ich bin ein großer Fan dieser Lösung, da ich Ereignisse mit Umfang wirklich nicht mag. Ich arbeite jedoch nicht jeden Tag mit VueJS, daher bin ich gespannt, ob es jemanden gibt, der Probleme mit diesem Ansatz sieht.
Webnet
2
Einfachste Lösung aller Antworten
Vikash Gupta
1
nett, kurz und einfach zu implementieren, auch leicht zu verstehen
nada
1
Wenn Sie nur ausschließlich direkte Geschwisterkommunikation wünschen, verwenden Sie $ parent anstelle von $ root
Malkev
46

Kommunikationsarten

Beim Entwerfen einer Vue-Anwendung (oder einer komponentenbasierten Anwendung) gibt es verschiedene Kommunikationstypen, die davon abhängen, mit welchen Problemen wir uns befassen, und sie haben ihre eigenen Kommunikationskanäle.

Geschäftslogik: Bezieht sich auf alles, was für Ihre App und deren Ziel spezifisch ist.

Präsentationslogik: Alles, mit dem der Benutzer interagiert oder das sich aus der Interaktion des Benutzers ergibt.

Diese beiden Bedenken beziehen sich auf diese Art der Kommunikation:

  • Anwendungsstatus
  • Eltern-Kind
  • Kind-Eltern
  • Geschwister

Jeder Typ sollte den richtigen Kommunikationskanal verwenden.


Kommunikationskanäle

Ein Kanal ist ein loser Begriff, mit dem ich mich auf konkrete Implementierungen beziehe, um Daten rund um eine Vue-App auszutauschen.

Requisiten: Eltern-Kind-Präsentationslogik

Der einfachste Kommunikationskanal in Vue für die direkte Eltern-Kind- Kommunikation. Es sollte hauptsächlich verwendet werden, um Daten in Bezug auf die Präsentationslogik oder einen eingeschränkten Datensatz in der Hierarchie weiterzugeben.

Refs und Methoden: Präsentation Anti-Pattern

Wenn es keinen Sinn macht, eine Requisite zu verwenden, damit ein Kind ein Ereignis von einem Elternteil behandelt, ist es in Ordnung , eine refKomponente für die untergeordnete Komponente einzurichten und ihre Methoden aufzurufen .

Tu das nicht, es ist ein Anti-Muster. Überdenken Sie Ihre Komponentenarchitektur und Ihren Datenfluss. Wenn Sie eine Methode für eine untergeordnete Komponente von einem Elternteil aufrufen möchten, ist es wahrscheinlich an der Zeit, den Status anzuheben oder die anderen hier oder in den anderen Antworten beschriebenen Methoden in Betracht zu ziehen.

Ereignisse: Präsentationslogik zwischen Kind und Eltern

$emitund $on. Der einfachste Kommunikationskanal für die direkte Kind-Eltern-Kommunikation. Auch hier sollte für die Präsentationslogik verwendet werden.

Ereignisbus

Die meisten Antworten bieten gute Alternativen für den Ereignisbus, der einer der Kommunikationskanäle für entfernte Komponenten ist, oder für alles andere.

Dies kann nützlich sein, wenn Sie Requisiten überall von weit oben bis zu tief verschachtelten untergeordneten Komponenten weitergeben, wobei fast keine anderen Komponenten diese dazwischen benötigen. Bei sorgfältig ausgewählten Daten sparsam verwenden.

Seien Sie vorsichtig: Die anschließende Erstellung von Komponenten, die sich an den Ereignisbus binden, wird mehrmals gebunden - was dazu führt, dass mehrere Handler ausgelöst werden und Lecks auftreten. Ich persönlich hatte nie das Bedürfnis nach einem Eventbus in all den Single-Page-Apps, die ich in der Vergangenheit entworfen habe.

Das Folgende zeigt, wie ein einfacher Fehler zu einem Leck führt, bei dem die ItemKomponente auch dann noch ausgelöst wird, wenn sie aus dem DOM entfernt wird.

Denken Sie daran, Listener im destroyedLifecycle-Hook zu entfernen .

Zentraler Speicher (Geschäftslogik)

Vuex ist der richtige Weg für die Zustandsverwaltung mit Vue . Es bietet viel mehr als nur Veranstaltungen und ist bereit für die vollständige Anwendung.

Und jetzt fragst du :

[S] Soll ich für jede kleinere Kommunikation den vuex-Shop erstellen?

Es scheint wirklich, wenn:

  • Umgang mit Ihrer Geschäftslogik,
  • Kommunikation mit einem Backend (oder einer beliebigen Datenpersistenzschicht wie lokalem Speicher)

So können sich Ihre Komponenten wirklich auf die Dinge konzentrieren, die sie sein sollen, und Benutzeroberflächen verwalten.

Das bedeutet nicht, dass Sie es nicht für die Komponentenlogik verwenden können, aber ich würde diese Logik auf ein Vuex-Modul mit Namespace mit nur dem erforderlichen globalen UI-Status übertragen.

Um zu vermeiden, dass in einem globalen Zustand ein großes Durcheinander entsteht, sollte der Speicher in mehrere Module mit Namespace unterteilt werden.


Komponententypen

Um all diese Kommunikationen zu koordinieren und die Wiederverwendbarkeit zu vereinfachen, sollten wir uns Komponenten als zwei verschiedene Typen vorstellen.

  • App-spezifische Container
  • Generische Komponenten

Auch hier bedeutet dies nicht, dass eine generische Komponente wiederverwendet werden sollte oder dass ein app-spezifischer Container nicht wiederverwendet werden kann, aber sie haben unterschiedliche Verantwortlichkeiten.

App-spezifische Container

Dies sind nur einfache Vue-Komponenten, die andere Vue-Komponenten (generische oder andere app-spezifische Container) umschließen. Hier sollte die Vuex-Store-Kommunikation stattfinden, und dieser Container sollte über andere einfachere Mittel wie Requisiten und Ereignis-Listener kommunizieren.

Diese Container könnten sogar überhaupt keine nativen DOM-Elemente haben und die generischen Komponenten könnten sich mit den Vorlagen und Benutzerinteraktionen befassen.

Umfang irgendwie eventsoder storesSichtbarkeit für Geschwisterkomponenten

Hier findet das Scoping statt. Die meisten Komponenten kennen den Store nicht und diese Komponente sollte (meistens) ein Store-Modul mit Namespace mit einem begrenzten Satz verwenden gettersund actionsmit den bereitgestellten Vuex-Bindungshilfen angewendet werden .

Generische Komponenten

Diese sollten ihre Daten von Requisiten erhalten, Änderungen an ihren eigenen lokalen Daten vornehmen und einfache Ereignisse ausgeben. Meistens sollten sie nicht wissen, dass es überhaupt einen Vuex-Store gibt.

Sie können auch als Container bezeichnet werden, da ihre alleinige Verantwortung darin besteht, sie an andere UI-Komponenten zu versenden.


Geschwisterkommunikation

Wie sollen wir nach all dem zwischen zwei Geschwisterkomponenten kommunizieren?

Anhand eines Beispiels ist es einfacher zu verstehen: Angenommen, wir haben ein Eingabefeld, und die Daten sollten über die App (Geschwister an verschiedenen Stellen im Baum) gemeinsam genutzt und mit einem Backend beibehalten werden.

Beginnend mit dem Worst-Case-Szenario würde unsere Komponente Präsentation und Geschäftslogik mischen .

// 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>

Um diese beiden Probleme zu trennen, sollten wir unsere Komponente in einen app-spezifischen Container packen und die Präsentationslogik in unserer generischen Eingabekomponente beibehalten.

Unsere Eingabekomponente ist jetzt wiederverwendbar und kennt weder das Backend noch die Geschwister.

// 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>

Unser app-spezifischer Container kann nun die Brücke zwischen der Geschäftslogik und der Präsentationskommunikation sein.

// 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>

Da die Vuex Speicher Aktionen mit der Back - End - Kommunikation beschäftigen, hier unsere Behälter brauchen nicht über axios und das Backend kennen.

Emile Bergeron
quelle
3
Stimmen Sie dem Kommentar zu, dass Methoden " die gleiche Kopplung wie die Verwendung von Requisiten " sind
Ghybs
Ich mag diese Antwort. Aber könnten Sie bitte näher auf Event Bus eingehen und "Seien Sie vorsichtig:" beachten? Vielleicht können Sie ein Beispiel geben, ich verstehe nicht, wie Komponenten zweimal gebunden werden könnten.
Vandroid
Wie kommunizieren Sie zwischen der übergeordneten Komponente und der untergeordneten Komponente, beispielsweise bei der Formularüberprüfung? Wo die übergeordnete Komponente eine Seite ist, ist das Kind das Formular und das Enkelkind das Eingabeformularelement?
Lord Zed
1
@vandroid Ich habe ein einfaches Beispiel erstellt, das ein Leck zeigt, wenn die Listener nicht richtig entfernt werden, wie alle Beispiele in diesem Thread.
Emile Bergeron
@ LordZed Es kommt wirklich darauf an, aber nach meinem Verständnis Ihrer Situation sieht es nach einem Designproblem aus. Vue sollte hauptsächlich für die Präsentationslogik verwendet werden. Die Formularvalidierung sollte an anderer Stelle erfolgen, z. B. in der Vanilla JS-API-Oberfläche, die eine Vuex-Aktion mit den Daten aus dem Formular aufrufen würde.
Emile Bergeron
10

Okay, wir können zwischen Geschwistern über Eltern mithilfe von v-onEreignissen kommunizieren .

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

Nehmen wir an, wir möchten die DetailsKomponente aktualisieren , wenn wir auf ein Element klicken List.


in Parent:

Vorlage:

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

Hier:

  • v-on:select-itemEs ist ein Ereignis, das in der ListKomponente aufgerufen wird (siehe unten).
  • setSelectedItemEs ist eine ParentMethode zum Aktualisieren selectedModel.

JS:

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

In List:

Vorlage:

<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"
  },
}
//...

Hier:

  • this.$emit('select-item', item)sendet Artikel über select-itemdirekt im übergeordneten Element . Und die Eltern senden es an die DetailsAnsicht
Sergei Panfilov
quelle
5

Was ich normalerweise mache, wenn ich die normalen Kommunikationsmuster in Vue "hacken" möchte, insbesondere jetzt, wo sie .syncveraltet sind, ist, einen einfachen EventEmitter zu erstellen, der die Kommunikation zwischen Komponenten verwaltet. Aus einem meiner neuesten Projekte:

import {EventEmitter} from 'events'

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

Mit diesem TransmitterObjekt können Sie dann in jeder Komponente Folgendes tun:

import Transmitter from './Transmitter'

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

Und um eine "empfangende" Komponente zu erstellen:

import Transmitter from './Transmitter'

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

Auch dies ist für wirklich spezifische Zwecke. Basieren Sie Ihre gesamte Anwendung nicht auf diesem Muster, Vuexsondern verwenden Sie stattdessen so etwas wie .

Hector Lorenzo
quelle
1
Ich verwende es bereits vuex, aber sollte ich für jede kleinere Kommunikation den vuex-Speicher erstellen?
Sergei Panfilov
Es fällt mir schwer, mit dieser Menge an Informationen zu sagen, aber ich würde sagen, wenn Sie bereits vuexyes verwenden, versuchen Sie es. Benutze es.
Hector Lorenzo
1
Eigentlich würde ich nicht zustimmen, dass wir Vuex für jede kleinere Kommunikation verwenden müssen ...
Victor
Nein, natürlich nicht, alles hängt vom Kontext ab. Eigentlich entfernt sich meine Antwort von Vuex. Andererseits habe ich festgestellt, dass ich mich umso weniger auf die Kommunikation zwischen Objekten verlasse, je mehr Sie Vuex und das Konzept eines zentralen Zustandsobjekts verwenden. Aber ja, stimme zu, es hängt alles davon ab.
Hector Lorenzo
3

Wie mit der Kommunikation zwischen Geschwistern umgegangen wird, hängt von der jeweiligen Situation ab. Aber zuerst möchte ich betonen, dass der globale Eventbus-Ansatz in Vue 3 wegfällt . Siehe diesen RFC . Deshalb habe ich beschlossen, eine neue Antwort zu schreiben.

Niedrigstes gemeinsames Ahnenmuster (oder „LCA“)

In einfachen Fällen empfehle ich dringend, das Muster "Niedrigster gemeinsamer Vorfahr" zu verwenden (auch als "Daten nach unten, Ereignisse nach oben" bezeichnet). Dieses Muster ist einfach zu lesen, zu implementieren, zu testen und zu debuggen.

Im Wesentlichen bedeutet dies, wenn zwei Komponenten kommunizieren müssen, ihren gemeinsamen Status in die nächstgelegene Komponente zu setzen, die beide als Vorfahren gemeinsam nutzen. Übergeben Sie Daten von der übergeordneten Komponente über Requisiten an die untergeordnete Komponente und übergeben Sie Informationen von der untergeordneten Komponente an die übergeordnete Komponente, indem Sie ein Ereignis ausgeben (siehe Beispiel hierfür am Ende dieser Antwort).

Wenn in einer E-Mail-App beispielsweise die Komponente "An" mit der Komponente "Nachrichtentext" interagieren muss, kann der Status dieser Interaktion in ihrem übergeordneten Element (möglicherweise einer Komponente mit dem Namen email-form) gespeichert werden . Möglicherweise haben Sie eine Requisite im email-formangerufenen Bereich, addresseedamit der Nachrichtentext Dear {{addressee.name}}der E-Mail basierend auf der E-Mail-Adresse des Empfängers automatisch vorangestellt werden kann .

Die Ökobilanz wird lästig, wenn die Kommunikation lange Strecken mit vielen Zwischenhändlern zurücklegen muss. Ich verweise oft Kollegen auf diesen ausgezeichneten Blog-Beitrag . (Ignorieren Sie die Tatsache, dass die Beispiele Ember verwenden. Die Ideen sind auf viele UI-Frameworks anwendbar.)

Datencontainermuster (z. B. Vuex)

Verwenden Sie in komplexen Fällen oder Situationen, in denen die Eltern-Kind-Kommunikation zu viele Zwischenhändler umfasst, Vuex oder eine gleichwertige Datencontainertechnologie. Verwenden Sie gegebenenfalls Module mit Namespace .

Beispielsweise kann es sinnvoll sein, einen separaten Namespace für eine komplexe Sammlung von Komponenten mit vielen Verbindungen zu erstellen, z. B. eine voll funktionsfähige Kalenderkomponente.

Publish / Subscribe (Event Bus) Muster

Wenn das Ereignisbusmuster (oder das Muster "Veröffentlichen / Abonnieren") besser für Ihre Anforderungen geeignet ist, empfiehlt das Vue-Kernteam jetzt die Verwendung einer Drittanbieter-Bibliothek wie z. B. mitt . (Siehe den in Absatz 1 genannten RFC.)

Bonus-Streifzüge und Code

Hier ist ein grundlegendes Beispiel für die Lowest Common Ancestor-Lösung für die Kommunikation zwischen Geschwistern, die über das Spiel Whack-a-Mole veranschaulicht wird .

Ein naiver Ansatz könnte sein, zu denken: "Maulwurf 1 sollte Maulwurf 2 anweisen, zu erscheinen, nachdem er geschlagen wurde." Vue rät jedoch von dieser Art von Ansatz ab, da wir in Baumstrukturen denken sollen .

Dies ist wahrscheinlich eine sehr gute Sache. Eine nicht triviale App, bei der Knoten über DOM-Bäume hinweg direkt miteinander kommunizieren, wäre ohne ein Buchhaltungssystem (wie es Vuex bietet) nur sehr schwer zu debuggen. Darüber hinaus weisen Komponenten, die "Daten herunter, Ereignisse hoch" verwenden, tendenziell eine geringe Kopplung und eine hohe Wiederverwendbarkeit auf - beides äußerst wünschenswerte Merkmale, die die Skalierung großer Apps unterstützen.

In diesem Beispiel gibt ein Maulwurf ein Ereignis aus, wenn er geschlagen wird. Die Game-Manager-Komponente entscheidet über den neuen Status der App, und der Geschwister-Maulwurf weiß daher implizit, was zu tun ist, nachdem Vue erneut gerendert hat. Es ist ein etwas triviales Beispiel für den „niedrigsten gemeinsamen Vorfahren“.

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>

AlexMA
quelle