Wie die Vue Composition API Vue Mixins ersetzt

Avatar of Anthony Gore
Anthony Gore am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

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.