Ausmisten, aber nachhaltig!

Eine Einführung in das Vue 3 Composition API
Keine Kommentare

Vue 3 steht in den Startlöchern. Mit dem neuen Composition API kommt eine zusätzliche Möglichkeit, um Komponenten aufzubauen. Doch warum ist das überhaupt notwendig? Welche Vorteile ergeben sich aus der Verwendung des API und wie benutzt man es?

Es war gar nicht so einfach, einen passenden Titel für diesen Artikel zu finden. In diesem Beitrag geht es um das Composition API, das mit Vue 3 kommen wird. Schaut man im Wörterbuch nach dem Begriff „composition“, finden sich eine ganze Reihe deutscher Wörter, die passen würden: Zusammensetzung, Gemisch, Struktur, Anordnung, Verbindung, Arrangement. Ja, richtig. Beim Composition API geht es um den Aufbau von Komponenten. Es geht um die Art und Weise, wie Dinge zusammengesetzt werden können, damit am Ende eine lauffähige Komponente entsteht. Die soll innerhalb einer Vue-Anwendung natürlich mit allen anderen Komponenten (auch mit denen, die nicht mit dem Composition API gebaut wurden) kompatibel sein.

Hierbei ist es zunächst wichtig, zu verstehen, dass das Composition API optional ist. Wer es nicht verwenden will, lässt es einfach sein. Das bekannte und etablierte Verfahren zum Aufbau von Komponenten wie in Listing 1 dargestellt bleibt auch in Vue 3 weiterhin bestehen. Man muss sich keine Sorgen machen, dass mit dem Wechsel auf die neue Version große Umstrukturierungen oder Anpassungen im eigenen Quellcode notwendig werden. Mehr noch, das Composition API kann heute schon verwendet werden. Hierzu wird ein Plug-in bereitgestellt, das man relativ schnell mit npm install @vue/composition-api ins eigene Projekt bringen kann. Wenn das Composition API dann heruntergeladen und im Projekt vorhanden ist, darf man auf keinen Fall vergessen, es zu aktivieren. Das kann durch Vue.use(VueCompositionApi) erfolgen, zum Beispiel in der main.js, wenn das Projekt mit dem Vue CLI erzeugt wurde.

International JavaScript Conference

Effective Microservices Architecture In Node.js

by Tamar Stern (Palto Alto Networks)

React Components And How To Style Them

by Jemima Abu (Telesoftas)

Angular Camp 2020

Als Online- oder Präsenztraining!

Das 360°-Intensivtraining mit Angular-Koryphäe Manfred Steyer
Präsentiert von Entwickler Akademie

Warum das neue Composition API?

Nochmal zurück zu Listing 1. Es zeigt, wie eine Vue-Komponente klassisch aufgebaut wird (hier in Form einer Single File Component). Letztendlich wird im Bereich <script> ein JavaScript-Objekt erzeugt. Dieses besteht aus einer Reihe von vordefinierten Properties bzw. Optionen, mit denen Daten, Verhalten und auch das Aussehen der Komponente bestimmt werden. In Vue gibt es viele Stellen, um Optionen festzulegen oder Logik unterzubringen: props, data, computed, methods, components. Zusätzlich bestehen noch eine ganze Reihe sogenannter Lifecycle-Methoden, die hinzugenommen werden können, um in bestimmten Situationen zu reagieren und einzugreifen. Wenn man Wert darauf legt, eine Aktion durchzuführen, nachdem eine Komponente erzeugt wurde, wenn man in den Renderprozess eingreifen will oder einfach bei Veränderung der Daten in der Komponente informiert werden will, kommen Lifecycle-Methoden und Watcher ins Spiel.

Ein klarer Nachteil des klassischen Verfahrens ist es, dass man in ein Korsett gepresst wird. Man muss dieses Vue-Komponentenobjekt erzeugen und hat sich an seine Konventionen zu halten. Wenn beispielsweise aus dem UI heraus auf Geschäftslogik zugegriffen werden soll, muss die zugehörige Logik in den Kategorien methods oder computed untergebracht sein.

In ein enges Schema gezwungen zu werden, dürfte generell den wenigsten Entwicklern gefallen. Der Verlust von Implementierungsfreiheit schmerzt einfach. Doch das ist nicht das größte Problem. Viel stärker wiegt beim klassischen Ansatz (wie Listing 1 auch zeigt), dass der Quelltext sehr schnell unübersichtlich wird.

Unübersichtlich bedeutet schwieriger zu lesen und damit auch schlechter zu warten. Die Gefahr, dass die Komponente unübersichtlich werden kann, liegt vor allem darin, dass die Einstellungen und auch die Geschäftslogik nach dem Aufbau und der Struktur der Komponente (vorgegeben durch Vue) organisiert sind. Besser wäre es, wenn man die Komponente nach den eigenen Anforderungen beziehungsweise Features strukturieren könnte, zum Beispiel indem man die Komponente aus einer Reihe von Funktionen aufbaut. Das würde auch dem Wiederverwendbarkeitsgedanken entsprechen, denn solche Funktionen ließen sich in Modulen zusammenfassen und außerhalb der Komponente führen. Das Composition API bietet all diese Möglichkeiten. Im weiteren Verlauf des Artikels soll betrachtet werden, wie man die Komponente aus Listing 1 mit dem neuen API umsetzen würde.

<template>
  <div>
    <v-select class="country-search" v-model="selected" :options="options" @input="onSelection" @search="onSearch" />
  </div>
</template>
 
<script>
  import vSelect from "vue-select";
  import "vue-select/dist/vue-select.css";
  export default {
    name: "CountrySearch",
    components: { vSelect },
  data() {
    return { options: [], selected: "" }
  },
  methods: {
    onSelection(value) {
      fetch("https://restcountries.eu/rest/v2/name/" + value)
      .then(res => { res.json().then(json => {
        this.$parent.$emit('countryChanged', json.map(e => e.alpha2Code) [0].toLowerCase());
        })
      })
    },
    onSearch(search) {
      fetch("https://restcountries.eu/rest/v2/name/" + search).then(res => {
        res.json().then(json => (this.options = json.map(e => e.name)));
      })
    }
  }}
</script>
<style />

setup()

Alles beginnt mit der neuen Methode setup(), die durch das Composition API hinzukommt. Dabei handelt es sich um keine klassische Lifecycle-Methode, obwohl sie sich so anfühlt. Die Methode setup() wird nach dem Auflösen der props und nach dem Erzeugen der Komponenteninstanz aufgerufen. Alle anderen Lifecycle-Methoden sind zu diesem Zeitpunkt noch nicht gelaufen. Auch die Bereiche data, methods und computed werden erst nach dem Ausführen von setup() betrachtet und in die Komponente miteinbezogen. Wichtig an dieser Stelle ist es, zu verstehen, dass das Composition API nicht nach dem Entweder-oder-Prinzip funktioniert, sondern auch zusammen mit dem klassischen Verfahren eingesetzt werden kann. Mit Hilfe von setup() hat man nun die Möglichkeit, in der Komponente auszumisten und sie Stück für Stück aufzubauen. Um die Daten einer Komponente zusammenzuhalten, hat man früher in der Option data (Listing 1) eine Funktion hinterlegt, die ein Objekt mit allen Daten zurückgibt, die in der Komponente geführt werden. Auf diese Daten konnte man in der Komponente mit Hilfe von this zugreifen. Der Bereich data kann mit dem Composition API komplett entfallen. In der setup-Methode werden hierfür jetzt einfache Konstanten definiert wie Listing 2 zeigt. Die Daten selbst müssen allerdings in ein ref-Objekt verpackt werden, das ebenfalls vom API zur Verfügung gestellt wird (daher auch der entsprechende Import). Sinn und Zweck solcher ref-Datencontainern ist es, Reaktivität sicherzustellen. Das in Listing 1 und 2 abgedruckte Beispiel implementiert eine einfache Komponente zum Suchen von Ländern. Zu Beginn ist noch kein Land gefunden, daher ist options ein leeres Array. Sobald jedoch Länder gefunden werden, muss das options-Array aktualisiert werden, und das Aktualisieren der Daten im options-Array kann wiederrum dazu führen, dass Folgeaktionen, wie zum Beispiel ein Aktualisieren (Re-Rendering) der Unterkomponenten, erforderlich werden. Um das zu realisieren, müssen die Daten reaktiv sein. Solche Daten nennt man daher Reactive References (ref).

Alle Objekte, die man in setup() erzeugt (im Falle von Listing 2 sind es lediglich die zwei ref), müssen verpackt in einem Objekt zurückgegeben werden. So kann Vue alle Bestandteile der setup-Methode bei der Komponentenerzeugung berücksichtigen.

<script>
  import { ref } from "@vue/composition-api"
  export default {
    name: "CountrySearch",
    components: { vSelect },
    setup() {
      const options = ref([])
      const selected = ref("")
      return { options, selected }
    },
...
  }
</script>

Beim klassischen Aufbau von Komponenten werden die Methoden, die auf dieser Komponente ausgeführt werden können, ebenfalls in ein Objekt verpackt, das an der Property methods hängt. Der Blick in Listing 1 zeigt, dass die Komponente über zwei Methoden verfügt. Die Methode onSearch wird bei jeder Eingabe im Suchfeld ausgeführt und initiiert eine Suchanfrage gegen ein REST API. Im Anschluss werden die Suchergebnisse aufbereitet und in this.options gesetzt, die Komponente rendert neu. Alle Datenfelder (hier options), die über data definiert wurden, sind über this ansprechbar. Listing 3 zeigt, wie die gleiche Methode mit dem Composition API umgesetzt wird. Es wird eine Konstante mit dem Namen onSearch definiert und dieser eine Funktion zugewiesen, die die Geschäftslogik enthält. Hier ist es wichtig, zu beachten, dass die Geschäftslogik nahezu unverändert verschoben werden konnte. Wenn man genauer hinschaut, gibt es aber doch eine kleine Veränderung. Nachdem der REST-Aufruf durchgeführt wurde, wird im then-Zweig des Promise options neu gesetzt. Da options in diesem Fall, wie zuvor bereits beschrieben, eine Reactive Reference ist, kann das Ergebnis nicht einfach zugewiesen werden. Auf der ref muss der Wert über .value – eine Art Setter, der gleichzeitig auch Getter ist – zugewiesen werden. Das ist ein wichtiges Detail. Beim weiteren Betrachten der Logik dürfte auch auffallen, dass options nicht auf this gesetzt wird. In setup() gibt es kein this, jedenfalls nicht in der Form, in der man es vom klassischen Ansatz her kennt. Das liegt daran, dass setup() eigentlich nur eine Art Konstruktor darstellt, aus dem die resultierende Vue-Komponente entsteht. In der finalen Komponente ist this wie gewohnt vorhanden, aber zum Zeitpunkt des Zusammensetzens über setup() eben noch nicht.

Bei der zweiten Methode (onSelection) in Listing 3 wird das sogar zum richtigen Problem. Die Methode onSelection funktioniert nämlich so, dass nach dem Finden und Auswählen eines Landes über $emit ein Event ausgelöst wird, um das Land eine Komponente nach oben durchzureichen. Die darüberliegende Komponente würde sich darum kümmern, die Flagge des ausgewählten Landes anzuzeigen. In Listing 1 wird hier noch this.$parent.$emit ausgeführt. Das ist ein Standardvorgehen in Vue: In Komponenten-Bäumen werden Daten normalerweise über props von oben nach unten durchgereicht und über $emit von unten nach oben geschoben. Wenn onSelection über die setup()-Methode definiert werden soll, existiert aber kein this.

Aus diesem Grund bietet setup() optional zwei Parameter an: setup(props, context). Wie bereits erwähnt, wird setup() nach der Initialisierung der props aufgerufen, daher können die props auch optional in die setup-Methode hineingereicht werden. Mit context erhält man schließlich den Ersatz für this:

  • this.$root > context.root

  • this.$parent > context.parent

  • this.$refs > context.parent

  • this.$listeners > context.listeners

  • this.$emit > context.emit

Mit Hilfe von context ist es ein Leichtes, die übergeordnete Komponente (context.parent) zu referenzieren und über $emit ein Event an sie zu schicken. Listing 3 zeigt, wie das funktioniert.

<script>
  import { ref } from „@vue/composition-api“
  export default {
    name: "CountrySearch",
    components: { vSelect },
    setup(props, context) {
      const selected = ref("")
      const options = ref([])
 
      const onSearch = (search) => {
        fetch("https://restcountries.eu/rest/v2/name/" + search).then(res => {
          res.json().then(json => (options.value = json.map(e => e.name)));
        })
      }
 
      const onSelection = (sel) => {
        fetch("https://restcountries.eu/rest/v2/name/" + sel).then(res => {
          res.json().then(json => {
            context.parent.$emit('countryChanged', json.map(e => e.alpha2Code)[0].toLowerCase());
          })
        })
      }
      return { selected, options, onSearch, onSelection }
    }
    ...
  }
</script>

Composition Functions

Beim Betrachten von Listing 3 fällt ein Problem auf, das mit der setup-Methode einhergehen kann. Wenn man jetzt nämlich den kompletten Aufbau einer Komponente hierhin verlagert, besteht dann nicht wieder die Gefahr, zu große und unübersichtliche setup-Methoden zu bauen? Die Antwort ist: Ja, die Gefahr besteht. Und nein, durch die Tatsache, dass die setup-Methode ausprogrammiert wird, stehen nun auch alle Möglichkeiten zur Strukturierung und Aufteilung in Teilfunktionen offen.

Das Composition API wird vom Vue-Entwicklerteam nicht ohne Grund als fortgeschrittenes Feature bezeichnet. Klar ist, dass es eine ordentliche Portion Weitsicht und Erfahrung benötigt, um Wildwuchs in der setup-Methode zu vermeiden. Auf der anderen Seite bietet sich so aber die wunderbare Möglichkeit, Komponenten endlich nach Logik zu strukturieren. Und wenn man geschickt an die Sache herangeht, erreicht man Wiederverwendbarkeit – ein Zustand, nach dem Entwickler ja grundsätzlich streben.

In Listing 4 kann man sehen, wie die setup-Methode gemäß ihrer Logik (Suche ausführen, Suchergebnis merken und weiterreichen) in Teilfunktionen zerlegt wird. Zunächst wird ref (options) zum Halten der Suchergebnisse definiert und auch die Suchlogik an sich in einer Funktion useSearch zusammengefasst. Als Ergebnis gibt useSearch sowohl die ref als auch die Logik in Form eines Objekts zurück. Gleiches gilt für die Funktionalität zur Auswahl eines Landes in useSelection: Es wird eine ref (selection) zum Halten des ausgewählten Landes definiert und eine Funktion, die bestimmt, was passieren soll, wenn ein Land ausgewählt wird. Diese Art von Funktionen nennt man Composition Functions. Genau die sind ein ganz zentrales Konzept des Composition API, um zu gut strukturierten, auch nachhaltig pflegbaren Komponenten zu kommen. Arbeitet man mit Composition Functions, kann setup() auf ein Minimum zusammengedampft werden (Listing 5). Zu beachten gilt hier jedoch, dass aus der setup-Methode immer ein Objekt zurückgegeben werden muss. Wenn man also eine Komponente aus mehreren Composition Functions zusammenbaut, kann man diese nicht einfach nur aufrufen. Das würde zu einem Fehler führen. Stattdessen sind mittels Destructuring und dem Operator die Objekte der einzelnen Composition Functions auseinanderzunehmen und zu einem(!) komplett neuen Objekt zusammenzusetzen.

Datei: /use/useSearch.js
import { ref } from '@vue/composition-api'
export function useSearch() {
  const options = ref([])
  const onSearch = (search) => {
    fetch("https://restcountries.eu/rest/v2/name/" + search).then(res => {
      res.json().then(json => (options.value = json.map(e => e.name)));
    })
  }
  return { options, onSearch }
}
 
export function useSelection(parent) {
  const selected = ref("")
  const onSelection = (selection) => {
    fetch("https://restcountries.eu/rest/v2/name/" + selection).then(res => {
      res.json().then(json => {
        parent.$emit('countryChanged', json.map(e => e.alpha2Code)[0].toLowerCase());
      })
    })
  }
  return { selected, onSelection }
}
<script>
  import { useSearch, useSelection } from "../use/useCountries"
  export default {
    name: "CountrySearch",
    setup( props, { parent }) {
      return { ...useSearch(), ...useSelection(parent) }
    }
    ...
  }
</script>

computed, Watchers und Lifecycle-Methoden

Wie bereits zuvor erwähnt wurde, bekommt der Entwickler mit dem Composition API eine sehr mächtige Alternative an die Hand, um Komponenten zu bauen. Die vorangegangenen Abschnitte haben sich darauf fokussiert, die Konzepte und den grundlegenden Aufbau der setup-Methode mit Composition Functions zu skizzieren. Mit dem Composition API können aber auch Datenfelder definiert werden, die erst zur Laufzeit ausgewertet werden (sogenannte Computed Values). In einer klassischen Vue-Komponente würde man diese wie folgt schreiben:

computed: {
  flagCss() { return "flag flag-" + this.country },
  ... weitere computed values...

Im Rahmen des Composition API legt man Computed Values durch die Funktion computed an. Diese wird vom API zur Verfügung gestellt und muss importiert werden, bevor man damit ein reaktives Computed Value erzeugen kann:

import { computed } from '@vue/composition-api'
export default {
  setup(props) {
  const flagCss = computed( () => "flag flag-" + props.country)
  return { flagCss }
}

Genauso wie computed importiert werden muss, um Computed Values zu implementieren, kann man auch watch beziehungsweise watchEffect importieren. Hiermit erhält man die Möglichkeit, Watchers, die früher mit this.$watch umgesetzt wurden, über die setup-Methode zu definieren. Wenn man einen Watcher mit watchEffect definiert, wird man bei jeder internen Datenveränderung informiert, beziehungsweise wird die dem watchEffect übergebene Callback-Funktion aufgerufen. Mit watch kann man noch explizierter werden und den Fokus beispielsweise auf ein bestimmtes zu überwachendes ref legen. Im Gegensatz zu watchEffect bietet watch auch die Möglichkeit, bei der Veränderung von Zuständen nicht nur den aktuellen Wert, sondern auch den vorangegangenen auszuwerten.

Die Abbildung der Lifecycle-Methoden in setup() ist ebenfalls möglich. Im Zusammenspiel mit dem Composition API beginnen die einzelnen Lifecycle-Methoden mit on, wie etwa onMounted. Um die Lifecycles in der setup-Methode zu verwenden, müssen sie – genauso wie computed, watchEffect und watch – importiert werden. Folgende Lifecycles stehen zur Verfügung:

  • beforeMount > onBeforeMount

  • mounted > onMounted

  • beforeUpdate > onBeforeUpdate

  • updated > onUpdated

  • beforeDestroy > onBeforeDestroy

  • destroyed > onDestroyed

  • errorCaptued > onErrorCaptured

Die Lifecycles beforeCreate und created, wie sie in Vue 2.x bestehen, können innerhalb der setup-Methode nicht verwendet werden. Bei näherer Betrachtung erschließt sich auch, warum: Diese beiden Lifecycles sind im Prinzip die setup-Methode. Die anderen Lifecycles, wie in der Liste oben dargestellt, können ähnlich verwendet werden wie computed, watchEffect und watch: Es wird die zugehörige on-Funktion aufgerufen und eine Funktion an dieselbige übergeben. Diese dient als Callback und wird ihrerseits aktiviert, wenn der entsprechende Moment im Lebenszyklus der Komponente eintritt.

Zusammenfassung

Anwendungen, die für Vue 2.x geschrieben wurden, sollten auch mit Vue 3 kompatibel sein und ohne größere Änderungen laufen. Im nächsten Major Release wird mit dem Composition API ein optionales, aber dennoch sehr mächtiges API geliefert, um Komponenten auf alternative Art und Weise zu strukturieren und zusammenzusetzen. Richtig und wohl überlegt eingesetzt, kann man mit dem neuen API der Gefahr vorbeugen, dass Komponenten unübersichtlich, hässlich und schwer wartbar werden. Die Grundlage hierfür schafft eine zentrale setup-Methode, die von Vue beim Initialisieren einer Komponente ganz zu Beginn aufgerufen wird. In dieser setup-Methode kann die Komponente dann zusammengesetzt werden, wobei es möglich ist, sie durch Aufteilung in Unterfunktionen (Composition Functions) gemäß ihrer logischen Struktur aufzubauen. Auch die Entwicklung von wiederverwendbaren Composition Functions ist denkbar. Auf GitHub findet sich begleitend zu diesem Artikel ein Beispielprojekt, einmal umgesetzt in klassischem Vue 2.x [1] und einmal unter Verwendung des neuen Vue 3 Composition API [2].

Somit schließt sich der Kreis zum vielleicht etwas ungewöhnlichen Titel für diesen Artikel. Mit dem Composition API bekommt man neue Freiheiten, aber auch Verantwortung. Wer mag, kann seine Komponenten nun im wahrsten Sinne des Wortes ausmisten und für besser strukturierten, gut lesbaren und übersichtlicheren Code sorgen. Die Nachhaltigkeit kommt dabei ganz automatisch, denn richtig eingesetzt kann mit den neuen Möglichkeiten von Vue 3 ein Aufbau geschaffen werden, der leicht gelesen, verstanden und auch gewartet werden kann.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -