Überblick
In Vue.js 2.x model.sync
wird 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 .
Und:
.once
und.sync
sind veraltet. Requisiten sind jetzt immer in eine Richtung. Um Nebenwirkungen im übergeordneten Bereich zu erzeugen, muss eine Komponente explizitemit
ein 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
store
undevent
hat 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 stores
Sichtbarkeit für Geschwisterkomponenten. (Oder vielleicht habe ich die obige Idee nicht verstanden.)
Frage
Was ist also der richtige Weg, um zwischen Geschwisterkomponenten zu kommunizieren?
quelle
$emit
kombiniert mitv-model
zu emulieren.sync
. Ich denke, Sie sollten den Vuex-Weg gehenAntworten:
Mit Vue 2.0 verwende ich den eventHub-Mechanismus, wie in der Dokumentation gezeigt .
Definieren Sie einen zentralen Ereignis-Hub.
Jetzt können Sie in Ihrer Komponente Ereignisse mit ausgeben
Und um zuzuhören, tust du es
Update Bitte lesen Sie die Antwort von @alex , die eine einfachere Lösung beschreibt.
quelle
this.$root.$emit()
undthis.$root.$on()
Sie können es sogar kürzer machen und die Root-
Vue
Instanz als globalen Event Hub verwenden:Komponente 1:
Komponente 2:
quelle
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:
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 , eineref
Komponente 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
$emit
und$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
Item
Komponente auch dann noch ausgelöst wird, wenn sie aus dem DOM entfernt wird.Code-Snippet anzeigen
Denken Sie daran, Listener im
destroyed
Lifecycle-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 :
Es scheint wirklich, wenn:
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.
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.
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
getters
undactions
mit 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 .
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.
Unser app-spezifischer Container kann nun die Brücke zwischen der Geschäftslogik und der Präsentationskommunikation sein.
Da die Vuex Speicher Aktionen mit der Back - End - Kommunikation beschäftigen, hier unsere Behälter brauchen nicht über axios und das Backend kennen.
quelle
Okay, wir können zwischen Geschwistern über Eltern mithilfe von
v-on
Ereignissen kommunizieren .Nehmen wir an, wir möchten die
Details
Komponente aktualisieren , wenn wir auf ein Element klickenList
.in
Parent
:Vorlage:
Hier:
v-on:select-item
Es ist ein Ereignis, das in derList
Komponente aufgerufen wird (siehe unten).setSelectedItem
Es ist eineParent
Methode zum AktualisierenselectedModel
.JS:
In
List
:Vorlage:
JS:
Hier:
this.$emit('select-item', item)
sendet Artikel überselect-item
direkt im übergeordneten Element . Und die Eltern senden es an dieDetails
Ansichtquelle
Was ich normalerweise mache, wenn ich die normalen Kommunikationsmuster in Vue "hacken" möchte, insbesondere jetzt, wo sie
.sync
veraltet sind, ist, einen einfachen EventEmitter zu erstellen, der die Kommunikation zwischen Komponenten verwaltet. Aus einem meiner neuesten Projekte:Mit diesem
Transmitter
Objekt können Sie dann in jeder Komponente Folgendes tun:Und um eine "empfangende" Komponente zu erstellen:
Auch dies ist für wirklich spezifische Zwecke. Basieren Sie Ihre gesamte Anwendung nicht auf diesem Muster,
Vuex
sondern verwenden Sie stattdessen so etwas wie .quelle
vuex
, aber sollte ich für jede kleinere Kommunikation den vuex-Speicher erstellen?vuex
yes verwenden, versuchen Sie es. Benutze es.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 imemail-form
angerufenen Bereich,addressee
damit der NachrichtentextDear {{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“.
quelle