Möchten Sie Code zwischen Ihren Vue-Komponenten teilen? Wenn Sie mit Vue 2 vertraut sind, haben Sie wahrscheinlich dafür ein Mixin verwendet. Aber die neue Composition API, die jetzt als Plugin für Vue 2 und als kommendes Feature von Vue 3 verfügbar ist, bietet eine viel bessere Lösung.
In diesem Artikel werden wir uns die Nachteile von Mixins ansehen und wie die Composition API diese überwindet und Vue-Anwendungen weitaus skalierbarer macht.
Mixins im Überblick
Lassen Sie uns kurz das Mixin-Muster überprüfen, da es wichtig ist, es für das, was wir in den nächsten Abschnitten behandeln werden, im Gedächtnis zu behalten.
Normalerweise wird eine Vue-Komponente durch ein JavaScript-Objekt mit verschiedenen Eigenschaften definiert, die die benötigte Funktionalität darstellen – Dinge wie data, methods, computed und so weiter.
// MyComponent.js
export default {
data: () => ({
myDataProperty: null
}),
methods: {
myMethod () { ... }
}
// ...
}
Wenn wir dieselben Eigenschaften zwischen Komponenten teilen möchten, können wir die gemeinsamen Eigenschaften in ein separates Modul extrahieren
// MyMixin.js
export default {
data: () => ({
mySharedDataProperty: null
}),
methods: {
mySharedMethod () { ... }
}
}
Jetzt können wir dieses Mixin zu jeder konsumierenden Komponente hinzufügen, indem wir es der Konfigurationseigenschaft mixin zuweisen. Zur Laufzeit werden die Eigenschaften der Komponente von Vue mit allen hinzugefügten Mixins zusammengeführt.
// ConsumingComponent.js
import MyMixin from "./MyMixin.js";
export default {
mixins: [MyMixin],
data: () => ({
myLocalDataProperty: null
}),
methods: {
myLocalMethod () { ... }
}
}
Für dieses spezielle Beispiel würde die zur Laufzeit verwendete Komponenten-Definition so aussehen
export default {
data: () => ({
mySharedDataProperty: null
myLocalDataProperty: null
}),
methods: {
mySharedMethod () { ... },
myLocalMethod () { ... }
}
}
Mixins gelten als „schädlich“
Mitte 2016 schrieb Dan Abramov „Mixins Considered Harmful“, in dem er argumentiert, dass die Verwendung von Mixins zur Wiederverwendung von Logik in React-Komponenten ein Anti-Pattern ist und stattdessen dafür plädiert, sich davon zu entfernen.
Dieselbe Nachteile, die er für React Mixins erwähnt, sind leider auch auf Vue anwendbar. Machen wir uns mit diesen Nachteilen vertraut, bevor wir uns ansehen, wie die Composition API sie überwindet.
Namenskollisionen
Wir haben gesehen, wie das Mixin-Muster zwei Objekte zur Laufzeit zusammenführt. Was passiert, wenn sie beide eine Eigenschaft mit demselben Namen teilen?
const mixin = {
data: () => ({
myProp: null
})
}
export default {
mixins: [mixin],
data: () => ({
// same name!
myProp: null
})
}
Hier kommt die Merge-Strategie ins Spiel. Dies ist die Regelmenge, die bestimmt, was passiert, wenn eine Komponente mehrere Optionen mit demselben Namen enthält.
Die Standard- (aber optional konfigurierbare) Merge-Strategie für Vue-Komponenten besagt, dass lokale Optionen Mixin-Optionen überschreiben. Es gibt jedoch Ausnahmen. Wenn wir beispielsweise mehrere Lifecycle-Hooks desselben Typs haben, werden diese einem Array von Hooks hinzugefügt und alle werden nacheinander aufgerufen.
Auch wenn wir keine tatsächlichen Fehler bekommen sollten, wird es immer schwieriger, Code zu schreiben, wenn man benannte Eigenschaften über mehrere Komponenten und Mixins hinweg jongliert. Es ist besonders schwierig, wenn Mixins von Drittanbietern als npm-Pakete mit eigenen benannten Eigenschaften hinzugefügt werden, die Konflikte verursachen könnten.
Implizite Abhängigkeiten
Zwischen einem Mixin und einer Komponente, die es verwendet, gibt es keine hierarchische Beziehung. Das bedeutet, dass eine Komponente eine in dem Mixin definierte Dateneigenschaft verwenden kann (z.B. mySharedDataProperty), aber ein Mixin kann auch eine Dateneigenschaft verwenden, die es als in der Komponente definiert annimmt (z.B. myLocalDataProperty). Dies ist häufig der Fall, wenn ein Mixin zur gemeinsamen Validierung von Eingaben verwendet wird. Das Mixin erwartet möglicherweise, dass eine Komponente einen Eingabewert hat, den es in seiner eigenen Validierungsmethode verwenden würde.
Dies kann jedoch zu Problemen führen. Was passiert, wenn wir eine Komponente später refaktorieren und den Namen einer Variablen ändern, die das Mixin benötigt? Wir werden beim Blick auf die Komponente nichts Falsches bemerken. Ein Linter wird es auch nicht erkennen. Wir sehen den Fehler erst zur Laufzeit.
Stellen Sie sich nun eine Komponente mit einer ganzen Menge von Mixins vor. Können wir eine lokale Dateneigenschaft refaktorieren oder wird dadurch ein Mixin kaputtgehen? Welches Mixin? Wir müssten sie alle manuell durchsuchen, um es zu wissen.
Migration von Mixins
Dans Artikel bietet Alternativen zu Mixins, darunter Higher-Order Components, Utility-Methoden und einige andere Muster der Komponentenkomposition.
Obwohl Vue in vielerlei Hinsicht React ähnelt, lassen sich die von ihm vorgeschlagenen alternativen Muster nicht gut auf Vue übertragen. Daher leiden Vue-Entwickler trotz dieses Artikels aus Mitte 2016 seit jeher unter Mixin-Problemen.
Bis jetzt. Die Nachteile von Mixins waren einer der Hauptgründe für die Composition API. Geben wir uns einen kurzen Überblick, wie sie funktioniert, bevor wir uns ansehen, wie sie die Probleme mit Mixins überwindet.
Composition API Crashkurs
Die Kernidee der Composition API ist, dass wir die Funktionalität einer Komponente (z.B. Zustand, Methoden, berechnete Eigenschaften usw.) nicht als Objekt-Eigenschaften definieren, sondern als JavaScript-Variablen, die von einer neuen Funktion setup zurückgegeben werden.
Nehmen wir dieses klassische Beispiel einer Vue 2-Komponente, die ein „Zähler“-Feature definiert
//Counter.vue
export default {
data: () => ({
count: 0
}),
methods: {
increment() {
this.count++;
}
},
computed: {
double () {
return this.count * 2;
}
}
}
Im Folgenden finden Sie genau dieselbe Komponente, die mit der Composition API definiert wurde.
// Counter.vue
import { ref, computed } from "vue";
export default {
setup() {
const count = ref(0);
const double = computed(() => count.value * 2)
function increment() {
count.value++;
}
return {
count,
double,
increment
}
}
}
Zuerst werden Sie bemerken, dass wir eine Funktion ref importieren, die es uns ermöglicht, eine reaktive Variable zu definieren, die sich ziemlich gleich verhält wie eine data-Variable. Gleiches gilt für die berechnete Funktion.
Die Methode increment ist nicht reaktiv, daher kann sie als normale JavaScript-Funktion deklariert werden. Beachten Sie, dass wir die Untereigenschaft value ändern müssen, um den Wert der reaktiven Variable count zu ändern. Das liegt daran, dass reaktive Variablen, die mit ref erstellt wurden, Objekte sein müssen, um ihre Reaktivität zu behalten, wenn sie weitergegeben werden.
Es ist eine gute Idee, die Vue Composition API Dokumentation für eine detaillierte Erklärung zu Rate zu ziehen, wie ref funktioniert.
Sobald wir diese Features definiert haben, geben wir sie aus der Setup-Funktion zurück. Es gibt keinen funktionalen Unterschied zwischen den beiden obigen Komponenten! Wir haben lediglich die alternative API verwendet.
Tipp: Die Composition API wird ein Kernbestandteil von Vue 3 sein, aber Sie können sie auch in Vue 2 mit dem NPM-Plugin @vue/composition-api verwenden.
Code-Extraktion
Der erste klare Vorteil der Composition API ist, dass Logik einfach extrahiert werden kann.
Lassen Sie uns die oben definierte Komponente mit der Composition API refaktorieren, so dass die von uns definierten Features in einem JavaScript-Modul useCounter liegen. (Das Präfix „use“ vor der Beschreibung eines Features ist eine Namenskonvention der Composition API.)
// useCounter.js
import { ref, computed } from "vue";
export default function () {
const count = ref(0);
const double = computed(() => count.value * 2)
function increment() {
count.value++;
}
return {
count,
double,
increment
}
}
Code-Wiederverwendung
Um dieses Feature in einer Komponente zu nutzen, importieren wir einfach das Modul in die Komponenten-Datei und rufen es auf (beachten Sie, dass der Import eine Funktion ist). Dies gibt die von uns definierten Variablen zurück, und wir können diese anschließend aus der Setup-Funktion zurückgeben.
// MyComponent.js
import useCounter from "./useCounter.js";
export default {
setup() {
const { count, double, increment } = useCounter();
return {
count,
double,
increment
}
}
}
Das mag auf den ersten Blick etwas ausführlich und sinnlos erscheinen, aber sehen wir uns an, wie dieses Muster die Probleme mit Mixins überwindet, die wir uns zuvor angesehen haben.
Namenskollisionen… gelöst!
Wir haben zuvor gesehen, wie ein Mixin Eigenschaften verwenden kann, die denselben Namen wie die der konsumierenden Komponente haben könnten, oder noch heimtückischer, wie die anderer Mixins, die von der konsumierenden Komponente verwendet werden.
Dies ist mit der Composition API kein Problem, da wir *jeden Zustand oder jede Methode explizit benennen müssen*, die aus einer Composition-Funktion zurückgegeben werden
export default {
setup () {
const { someVar1, someMethod1 } = useCompFunction1();
const { someVar2, someMethod2 } = useCompFunction2();
return {
someVar1,
someMethod1,
someVar2,
someMethod2
}
}
}
Namenskollisionen werden genauso gelöst wie bei jeder anderen JavaScript-Variable.
Implizite Abhängigkeiten… gelöst!
Wir haben auch zuvor gesehen, wie ein Mixin Daten-Eigenschaften verwenden kann, die in der konsumierenden Komponente definiert sind, was den Code fragil und sehr schwer verständlich machen kann.
Eine Composition-Funktion kann auch auf eine lokale Variable zurückgreifen, die in der konsumierenden Komponente definiert ist. Der Unterschied besteht jedoch darin, dass diese Variable nun explizit an die Composition-Funktion übergeben werden muss.
import useCompFunction from "./useCompFunction";
export default {
setup () {
// some local value the a composition function needs to use
const myLocalVal = ref(0);
// it must be explicitly passed as an argument
const { ... } = useCompFunction(myLocalVal);
}
}
Zusammenfassung
Das Mixin-Muster sieht auf den ersten Blick ziemlich sicher aus. Die gemeinsame Nutzung von Code durch Zusammenführen von Objekten wird jedoch zu einem Anti-Pattern, da es den Code anfällig macht und die Möglichkeit, die Funktionalität zu verstehen, verschleiert.
Das Genialste an der Composition API ist, dass sie es Vue ermöglicht, sich auf die in nativem JavaScript eingebauten Schutzmechanismen zu verlassen, um Code zu teilen, wie z.B. das Übergeben von Variablen an Funktionen und das Modulsystem.
Bedeutet das, dass die Composition API in jeder Hinsicht besser ist als die klassische API von Vue? Nein. In den meisten Fällen sind Sie gut beraten, bei der klassischen API zu bleiben. Aber wenn Sie Code wiederverwenden möchten, ist die Composition API unbestreitbar überlegen.
Ich liebe diese Erklärung und den Codevergleich. Dies ist die erste Erklärung der Composition API, die ich wirklich verstehe (zugegebenermaßen habe ich mich nicht tiefgehend damit beschäftigt). Mir war nicht bewusst, dass es darum ging, Probleme mit dem Mixin-Muster zu beheben, aber die Art und Weise, wie Sie es hier dargelegt haben, ergibt viel Sinn. Danke!
Großartig, danke, ich freue mich, dass es hilfreich war.
Es sollte sein
import { ref } from “@vue/composition-api”
Ich glaube, er geht davon aus, dass Vue 3 verwendet wird, und in diesem Fall importiert man die Methoden direkt aus „vue“.
Der andere Kommentator hat Recht, ich habe versucht, diesen Artikel zukunftssicher zu gestalten, indem ich Vue 3-Syntax verwendet habe. Aber wenn Sie das Plugin verwenden, wäre Ihr Code korrekt.
Toller Beitrag :)
Schnelle Frage: Wie ist es möglich, eine „Composition API“-Komponente aus einer „Classic Vue2“-Komponente zu importieren und zu verwenden?
Oder vielleicht auf Routebene, muss Ihre gesamte Route entweder „Classic Vue2“ oder „Composition API“ sein?
Hallo Cedric, soweit ich weiß, können Sie Options-API-Komponenten mit Composition-API-Komponenten im selben Projekt kombinieren, da sie im Grunde gleich sind, nur eine andere Art der Deklaration.
Alles klar, tatsächlich scheint es einfach zu funktionieren!
@vue/composition-apiin ein Projekt einbinden und die eine oder andere API verwenden.Ich habe versucht, das mit einem komplexeren Mixins-Beispiel zu verwenden, aber es hat nicht geklappt. Ich muss es noch einmal versuchen.
Danke :)
Schöner Artikel! Ich mag Mixins in Vue.js wirklich nicht
Toller Artikel! Danke.
Ich muss zugeben, dass das, was ich immer an Vue gegenüber React geliebt habe, darin bestand, dass Vue-Code einfach zu lesen war und ich nicht viel nachdenken musste, um ihn sehr gut schreiben zu können. Die Composition API ändert das, was wohl keine schlechte Sache ist, sie ist nur eine weitere Abstraktionsebene über das Standard-Vue-Komponentenmodell, die die Lernkurve für neue Entwickler erhöht, die auf Code in einer „useMyComponent“-Klasse stoßen. Ich kann mir vorstellen, dass Junior-Entwickler in meinem Team von diesem Muster frustriert sind, ohne die Einschränkungen seines Vorgängers zu kennen. Ich habe jedoch mit Mixin-Bugs gekämpft, die durch umbenannte Dateneigenschaften verursacht wurden, auf die sie angewiesen waren, kein Spaß, also verstehe ich es. Ich schweife ab. Danke für den Artikel!
Ich stimme zu, aber bedenken Sie, dass Sie die Composition API nicht verwenden müssen, da sie ein additives Feature ist
Das ist wunderschön, Anthony! Du bist wahrlich ein Wortkünstler und ein großartiger technischer Autor. Du hast mir die Gründe hinter der Composition API klarer gemacht. Du rockst!
Danke, mein Herr!
Danke für den Artikel!
Kurzer Hinweis: Bei #code-extraction sollte es count.value statt count sein.
Ich habe ein paar Minuten gebraucht, um das herauszufinden.
Von Adam Leis, nach Abpfiff
Ich habe eine Antwort gefunden und hoffe, sie ist hilfreich für Sie.
https://stackoverflow.com/questions/66604095/vue-3-composition-api-how-to-specify-a-prop-in-setup
Wie kann man die Composition API global einbinden (vielleicht in main.js) und sie in allen Komponenten verwenden, ohne sie in jeder einzelnen Komponente importieren zu müssen?
Toller Artikel!
Wie würde sich die Verwendung von MyMixinA, das ein anderes MyMixinB verwendet (mixins: [MyMixinB] in der Definition von MyMixinA), in der Welt der Composition API übersetzen? Setup von useB() und Übergabe des Ergebnisses an useA()?
Danke!
Danke fürs Teilen!
Wie sieht es mit der Wiederverwendung von Props-Definitionen innerhalb von Setup aus? Betrachten Sie ein solches Mixin
const mixinColor = {
props:{
color: {
type: String,
required: true
},
computed:{
colorInlineStyle(): object{
return {“background-color”:this.color,’–hover-color’:this.color};
},
}
const SectionHome = defineComponent(
mixins:[mixinColor],
}
Die berechnete Eigenschaft könnten wir mit einer Setup-Funktion ersetzen, aber können wir Props INNERHALB von setup() DEFINIEREN?
Wie Chris Coyier bemerkte – nicht wirklich.
Wie teilt man Lifecycle-Hooks zwischen?