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 ref
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.
// A component that binds to a custom 'update' event.
var Item = {
template: `<li>{{text}}</li>`,
props: {
text: Number
},
mounted() {
this.$root.$on('update', () => {
console.log(this.text, 'is still alive');
});
},
};
// Component that emits events
var List = new Vue({
el: '#app',
components: {
Item
},
data: {
items: [1, 2, 3, 4]
},
updated() {
this.$root.$emit('update');
},
methods: {
onRemove() {
console.log('slice');
this.items = this.items.slice(0, -1);
}
}
});
<script src="https://unpkg.com/vue@2.5.17/dist/vue.min.js"></script>
<div id="app">
<button type="button" @click="onRemove">Remove</button>
<ul>
<item v-for="item in items" :key="item" :text="item"></item>
</ul>
</div>
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 :
[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 events
oder stores
Sichtbarkeit 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 getters
und actions
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 .
// 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.
$emit
kombiniert mitv-model
zu emulieren.sync
. Ich denke, Sie sollten den Vuex-Weg gehen