Betrachtet man die drei großen, bekannten Frontend-Web-Frameworks Angular, React und Vue, so fühlt es sich so an, als läge Vue genau in der Mitte zwischen den anderen beiden. Viele Jahre habe ich mit Angular gearbeitet und dessen vorgegebene Pfade und eine gewisse Ausrichtung hinsichtlich Skalierbarkeit geschätzt. Seit Anfang des Jahres arbeite ich nun intensiv mit Vue und stelle natürlich Vergleiche an. Was mir unter anderem daran gut gefällt, ist der Gedanke, gut etablierte Konzepte von Angular und React zu kombinieren und etwas mehr Flexibilität zu bieten. In meiner täglichen Arbeit mit Vue fällt mir jedoch auf, dass das Framework aktuell noch ein wenig nachlegen könnte, was die Umsetzung von großen und komplexen Anwendungen betrifft.

Vue 3

Diese Tatsache hat natürlich auch bereits das Vue-Team selbst bemerkt, und es war ein wichtiger Punkt auf der Agenda der kürzlich erschienenen Vue Version 3. Neben einer kompletten internen Umstrukturierung des Quellcodes ist Vue 3 selbst vollkommen in TypeScript geschrieben. Die Unterstützung bei der Entwicklung mit TypeScript wurde daher erheblich verbessert. Zwar war dies in Vue 2 auch schon möglich, jedoch etwas eingeschränkt, und das Tooling ist zu großen Teilen auch abhängig von Implementierungsdetails wie z.B. der Visual Studio Code Editor-Erweiterung Vetur. Gerade in komplexen Anwendungen mit einer Vielzahl an domänenspezifischen Entitäten und Logik hilft einem ein Typ-System bei deren Modellierung. TypeScript assistiert bei der Fehlerbehebung und kann die Entwickler-Werkzeuge deutlich verbessern. Die größte und meiner Meinung nach wegweisendste Änderung in Vue 3 ist jedoch die neue Composition API, die wir uns in diesem Artikel genauer anschauen wollen.

Eine kleine Vue Beispielapplikation

Komponenten in Vue werden typischerweise als sogenannte Single File Components (kurz SFC) umgesetzt. Pro Vue Komponente gibt es daher eine .vue Datei, in der sich bis zu drei Teile befinden: ein Template mit Vue Markup (HTML Markup mit zusätzlichen Vue Direktiven, beispielsweise für Bedingungen oder Schleifen), welches das Rendering der Komponente beschreibt, ein Script-Tag, in der sich die Logik der Komponente befindet und ein Style-Tag, das beispielsweise CSS zum Styling der HTML Elemente enthält. Wie auch in anderen modernen Frontend-Frameworks bekommt das Template seine Daten über die aus dem Script bereitgestellten Eigenschaften.

Abbildung Vue 3 Composition API - Beispielapplikation: Die Lamafarm

Betrachten wir einmal folgende Beispielapplikation: Die Lamafarm zeigt eine Reihe von Lamas, die sich auf der Farm befinden. Über eine Suchleiste kann man Lamas nach Namen filtern. Ein Lama ist über seine Leinen-Farbe erkennbar. Jedes Lama lässt sich zum Spazierengehen ausführen. Mit den Pfeiltasten kann man es nach links und rechts auf der Wiese geleiten.

In Code-Beispiel 1 (siehe oben) ist der Script-Teil (in TypeScript) dieser Vue-Komponente aufgeführt. Eine Vue-Instanz erhält als Eingabe ein sogenanntes Options-Objekt, welche alle verschiedenen Eigenschaften und Verhaltensweisen einer Vue-Komponente beschreibt. Jede Eigenschaft dieses Objekts ist dabei für einen bestimmten Teilbereich der Komponente verantwortlich.

In unserem Beispiel benötigen wir das Property data. Es beschreibt die Daten, auf denen die Komponente arbeitet und welche im Template angezeigt werden können. Es enthält u.a. ein Array aller auf der Farm befindlichen Tiere (llamas), welches zu Beginn leer ist. Diese Liste wird erst beim Erstellen der Komponente über eine externe Schnittstelle geladen. Hierzu implementieren wir den Lifecycle-Hook mounted, der als Methode innerhalb des Options-Objekts zur Verfügung steht. Dieser Code wird ausgeführt, sobald die Komponente gerendert wird. Über die Eigenschaft searchText in data bilden wir den gerade vom User eingegebenen Suchtext ab. Wir kombinieren dies mit der Liste aller Lamas und definieren darüber filteredLlamas als computed Property. Diese werden immer dann neu ausgewertet, wenn sich von ihnen abhängige Werte ändern.

Um mit einem Lama spazieren zu gehen, möchten wir die Pfeiltasten der Tastatur verwenden. Wir registrieren uns daher beim mount-Lifecycle auf Tastendrücke und melden uns über beforeDestroy wieder von diesen Ereignissen ab. Die Methoden, die diese Events verarbeiten, als auch die Methoden, die wir über die Buttons in der Oberfläche auslösen, sind unter der Eigenschaft methods gesammelt.

Neben diesen Eigenschaften des Options-Objekts gibt es noch weitere wie z.B. die props, über die eine Komponente von außen Eingaben erhalten kann, watch, mit deren Hilfe man auf Änderungen bestimmter data Eigenschaften reagieren kann, oder weitere Lifecycle-Methoden.

Warum gibt es die Composition API?

Die Struktur des Option-Objekts ist sehr nachvollziehbar, da es die verschiedenen Bestandteile des Frameworks abbildet. Auf den ersten Blick ist diese Struktur sehr übersichtlich und bietet einen leichten Einstieg bei der Arbeit mit dem Framework. Ein Problem dieses Objekts ist jedoch, dass es sich nicht an der Fachlichkeit der Anwendung, sondern an den Bestandteilen des Frameworks orientiert. Dies hat zur Folge, dass es nur schwer möglich ist, zusammengehörige Features auch in sich geschlossen zu behandeln. Sie verteilen sich meist über mehrere Stellen.

Diesen Umstand können wir bereits bei unserer – zugegeben recht trivialen – Anwendung erkennen. Ein Teil der Logik zur Verarbeitung der Lama-Liste befindet sich in der mounted-Methode, die Filterung derer liegt in den computed Properties ab, ein Teil der Daten für die Spazier-Logik liegt in dem data Property ab, die Methoden zu deren Verarbeiten weiter unten im Block der methods, die Registrierung auf die Events wiederum in Lifecycle-Methoden. Es ist technisch recht schwierig diese Dinge für sich genommen aus der Komponente heraus zu trennen. Bei komplexeren Komponenten nimmt diese Verteilung weiter zu. So ertappt man sich recht schnell dabei, häufig in Vue-Komponenten im Options-Objekt hin- und her zu scrollen.

Abbildung 1 zeigt schematisch, wie die Verteilung der Domänen-Funktionalitäten in einer Vue Single File Component aktuell aussieht. Was wir uns allerdings eher wünschen, ist in Abbildung 2 dargestellt. Statt unsere Funktionalität über das gesamte Objekt zu verteilen, möchten wir sie eher blockweise bündeln und schließlich sogar unabhängig vom Rest heraus extrahieren können.

Abbildung 2 Vue 3 Composition API: Typischer Aufbau einer Vue Single File Component

Abbildung 1: Typischer Aufbau einer Vue Single File Component

Abbildung 2 Vue 3 Composition API: Wunsch-Aufbau einer Vue Component mit Möglichkeit der Feature-Extraktion

Abbildung 2: Wunsch-Aufbau einer Vue Component mit Möglichkeit der Feature-Extraktion

Wie könnte man nun diese verschiedenen Funktionalitäten (Laden und Initialisieren der Lama-Farm, Suchen der Lamas sowie Ausführen der Lamas) aus dem Gesamtkontext herauszulösen und in eigenständige Verantwortungsbereiche auslagern? Im Falle der Initialisierung kann man das noch recht gut umsetzen, indem man die API-Logik – soweit wie möglich – in reine, importierbare TypeScript Funktionen auslagert. Seine Grenze erreicht dieses Vorgehen sobald man mit Framework-spezifischen Bestandteilen wie Lifecycle-Hooks zusammenarbeiten möchte. Im Falle der Such- und Spazier-Funktionalität könnte man Vue-Mixins einsetzen. Diese Art von Vue-Teil-Komponenten lassen sich später in die Gesamt-Komponente einhängen, haben allerdings den großen Nachteil, dass man schwer unterscheiden kann, welche Funktionalität aus welchem Mixin stammt und sich in welcher Form konkret auf die Komponente auswirkt.

Die Composition API

Unter anderem um diesem grundsätzlichen Problem der “Unteilbarkeit” entgegenzuwirken, wurde die Composition API ins Leben gerufen. Sie ist offiziell Teil der neuen Vue Version 3, ist aber bereits für Vue 2 anhand des Composition API Plugins mit ein paar Einschränkungen einsetzbar. Dieses Plugin zu verwenden kann durchaus nützlich sein, da nicht jede Applikation unmittelbar auf die neue Vue Version migriert werden kann.

Die neue API ermöglicht es, Komponenten-Bestandteile eines Features, d.h. deren Data, Computed-Properties, Watches, Lifecycle-Hooks und Methoden, an einer Stelle gemeinsam zusammenzustellen und dann als gemeinsames Ganzes weiterzuverwenden.

Setup der Composition API

Um die Composition API einzusetzen, verwenden wir in unserem bereits existierenden Vue 2 Projekt das Composition API Plugin (die in diesem Artikel gezeigten Funktionalitäten werden von diesem bereits unterstützt). Wir installieren dazu das Plugin über npm:

npm install @vue/composition-api

und registrieren das Plugin in der main.ts:

Nun können wir die API nutzen. Am Beispiel der Suchfunktionalität werden wir diese jetzt als alleinstehendes Feature herausziehen und über die Composition API kapseln. Wir nennen dieses zusammengehörige Paket von nun an ein Composable. Um dieses in einer Komponente zu verwenden, wird das Options-Objekt durch die setup Lifecycle-Methode erweitert. Diese steht mit Einsatz der Composition API zur Verfügung und wird als allererstes aufgerufen, noch bevor die Komponente erstellt wird (denn in ihr können bereits wichtige Eigenschaften für den Bau der Komponente definiert werden). Wir halten unser Beispiel zunächst einfach, weshalb unser Composable das initiale Laden der Lamas beinhalten und auch nur Arrays von Lamas filtern kann. Dieses Feature könnte man sicherlich noch weiter generalisieren. Das Composable braucht weiterhin Wissen über den eingegebenen Suchtext, wird über Änderungen an diesem benachrichtigt und aktualisiert dann die gefilterte Liste der Lamas.

In Code-Beispiel 3 legen wir die setup-Methode an und beginnen mit der Extraktion der Suchfunktion. Diese nutzt Bestandteile der Composition API, auf die wir im Folgenden näher eingehen möchten.

ref

Als Erstes extrahieren wir unser Lama-Array aus der data Eigenschaft in die setup-Funktion und nutzen die neue ref-Funktion. Diese Hilfsfunktion ist einer der wichtigste Teile der Composition API und sorgt dafür, dass eine Variable reaktiv wird. Vue wird Änderungen an dieser Variable feststellen können, um dadurch davon abhängige Teile wie z.B. computed Properties erneut auszuwerten. Die Ref-Funktion kapselt nicht nur Objekte, sondern auch primitive Typen wie number oder boolean und kann so deren Wert-Änderung ebenfalls verfolgen. Neben der Lama-Liste definieren wir weiterhin unseren searchText als Ref.

Lifecycle-Methoden

Innerhalb der setup-Funktion können wir durch den Aufruf von on… gefolgt von dem Namen der gewünschten Lifecycle-Methode auf den jeweiligen Lebenszyklus der Komponente reagieren. Wir verschieben unsere Funktionalität von mounted des Options-Objekts in diesen Funktionsaufruf. Wir rufen unseren LamaService auf, warten auf das Ergebnis und weisen es dem Lama-Array zu. Zu beachten ist an dieser Stelle, dass wir den eigentlichen Wert einer Ref-Variablen über das value-Feld erreichen. Da die Variable reaktiv ist, werden nun wie zuvor die Lamas der Farm beim Anzeigen der Komponente geladen.

computed

Als nächstes kombinieren wir wieder unseren Suchtext mit der Liste aller Lamas, um die gefilterte Liste zu erhalten. Hier nutzen wir den Aufruf der computed-Funktion. In dieser verwenden wir beide zuvor definierten Ref-Variablen, indem wir nur die Lamas auswählen, deren Name Teile des Suchtextes enthalten. Somit verlagern wir die Implementierung von filteredLlamas aus der computed-Eigenschaft in die setup-Funktion.

Rückgabe-Wert der setup-Funktion

Alles, was derjenige, der unser Composable nutzen möchte, für die Anbindung benötigt, muss am Ende über ein Objekt herausgegeben werden. In unserem Falle geben wir den reaktiven Suchtext als auch das fertig berechnete, gefilterte Array der Lamas nach außen weiter.

Beides kann nun im Vue-Template direkt angebunden werden. Obwohl wir Ref-Variablen nach außen gegeben haben, können diese im Template ohne den Umweg über das value-Feld genutzt werden. Dieser Zwischenschritt wird vom Vue-Framework automatisch übernommen.

Extraktion des Composables

Werfen wir nun einen Blick in die setup-Funktion, so fällt auf, dass die darin enthaltene Logik normale TypeScript Funktionen sind, die nicht direkt mit der Lama-Komponente zusammenhängen müssen. Aus diesem Grund können wir das Composable auch vollständig aus der Komponente herauslösen und so unser Such-Feature auch in anderen Kontexten einsetzen (siehe Code-Beispiel 4).

Code-Beispiel 4 zeigt eine gängige Namenskonvention für eine solche Composition-Funktion. Ähnlich zu Vue-Plugins und angelehnt an das Hooks-Pattern bei React werden die Features mit dem use…() Präfix versehen. In Code-Beispiel 5 sehen wir schließlich, wie man diese ausgelagerte Funktion wiederum in einer konkreten Komponente nutzt. Dies geschieht analog zum vorherigen Inline-Code in der setup-Funktion.

Weitere Bestandteile der Composition API

Die hier gezeigten Funktionen der Composition API zeigen nur einen kleinen, aber wesentlichen Ausschnitt der gesamten API. Weitere häufig verwendete Funktionen sind beispielsweise die Folgenden:

Funktionen

Möchten wir zu einem bestimmten Zeitpunkt Informationen von außen in unser Composable hineingeben, können wir dies z.B. durch Funktionsaufrufe tun, die wir als Rückgabewert der setup-Funktion an die Komponente weiterreichen. Die Komponente kann diese Funktionen daraufhin aufrufen und so zusätzliche Informationen an das Composable überreichen.

Properties und Context

Die setup-Funktion hat die zwei Parameter props sowie context. Bei Ersterem werden die props-Eigenschaften einer Komponenten automatisch als reaktive Eigenschaften in die Funktion hereingegeben. Vom zweiten context-Parameter können wir typischerweise solche Komponenten-Eigenschaften bekommen, die vorher dem this-Objekt in Vue 2 zugehörig waren, wie etwa $root, $parent oder $emit, wobei wir letzteres auch in unseren Composables zum Auslösen von Ereignissen nutzen können.

watchEffect

Über watchEffect können wir eine Funktion implementieren, die sowohl direkt beim Start der setup-Funktion als auch bei jeder Änderung von in ihr befindlichen Abhängigkeiten erneut ausgeführt wird. Das Vue-Framework kümmert sich automatisch um die Erkennung von reaktiven Abhängigkeiten.

Vollständiges Refactoring des Beispiels

Führen wir die oben gezeigten Schritte auch für unsere anderen Features aus sowie ein paar Aufräumarbeiten, wandelt sich die komplette Struktur unserer Vue-Komponenten zu einer sehr schlanken Komponente, die nur noch die verschiedenen für sie wesentlichen Bestandteile einbindet, zusammenführt und nutzt. Code-Beispiel 6 zeigt den vollständigen Quelltext nach dem Refactoring aller Features als Composables.

Die verschiedenen Composables liegen als wiederverwendbare Composition-Funktionen vor:

composables/llamas-service.ts

composables/searchable-llamas.ts

composables/walkable-llama.ts

Wohin bringt uns Vue 3?

Gerade in diesem letzten Schritt sollte deutlich erkennbar sein, wie mächtig die Konzepte der neuen Composition API sind. Durch die Kombination von TypeScript und Konzepten der funktionalen Programmierung lassen sich komplexe Vue-Anwendungen deutlich modularer und skalierbarer aufbauen. Es können echt wiederverwendbare Elemente ohne Informationsverlust (der beispielsweise durch den Einsatz von Mixins stattfindet) erstellt und nahezu beliebig komplex miteinander verbunden werden. Es ist dadurch möglich, Vue-Komponenten so schlank zu halten, dass deren alleinige Aufgaben nur noch die Anbindung und das Rendern der in ihr verwendeten Bestandteile sind. Der in Composition-Funktionen gekapselte Code ist nur durch Lifecycle-Methoden mit Framework-spezifischen Code verbunden. Davon abgesehen sind es normale JavaScript bzw. TypeScript Funktionen. Die Ref-Funktion beispielsweise liefert ein standard JavaScript ES2015 Proxy-Objekt und ist demnach kein direkter Bestandteil von Vue.

Abstrahiert man die Framework-Ebene noch weiter, wäre es sogar vorstellbar, dass die damit umgesetzten Features auch in komplett anderen Frameworks nutzbar werden (beispielsweise als React-Hooks). Durch die Composition API schafft Vue damit Möglichkeiten zu komplett neuen architektonische Grundlagen. Im Sinne von “Build for Change” kann man den Kern seiner Anwendungslogik in reines TypeScript auslagern und Vue wirklich nur noch als View-Layer nutzen. Ich freue mich über das Release von Vue 3 und seine neuen Möglichkeiten. Ich hoffe, es geht Ihnen ähnlich. Lassen Sie es mich wissen.

Quellen und weitere nützliche Referenzen:

 

David Würfel

One thought on “Komplexe Anwendungen skalierbar mit der Vue 3 Composition API umsetzen

  1. Das sieht ja interessant aus. Wir arbeiten jetzt seit einiger Zeit mit Vue.js in der Version 2. Schön zu sehen, dass sich hier weiterhin viel tut. Gerade die Composition API werden wir demnächst wohl mal ausprobieren.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Die von Ihnen hier erhobenen Daten werden von der Host Europe GmbH zur Veröffentlichung Ihres Beitrags in diesem Blog verarbeitet. Weitere Informationen entnehmen Sie bitte folgendem Link: www.hosteurope.de/AGB/Datenschutzerklaerung/