Die Composition API in Vue.js 3 – Teil 1
vuejs advanced composition-api | Antony Konstantinidis • | 13 Minuten
Mit der neuen Version des Javascript Frameworks Vue.js kamen auch viele Neuerungen und Veränderungen. Eine davon ist die Composition API, die wir hier näher erläutern möchten.
Mit den Jahren wurde Vue.js reicher an Funktionen und immer mehr Entwickler nutzten das Framework auch für größere Projekte – und das meist in Teams. Die Folge: Die alte Vue API konnte mit den höheren Anforderungen nicht mehr so gut mithalten. Der Code wurde unübersichtlich und bremste die Prozesse aus. Das API-Konzept musste überdacht werden. Mit der neuen API sollte der User nun flexibler seinen Code gestalten können. Statt den Code nur mit Optionen innerhalb eines großen JavaScript-Objektes zu organisieren, wie es bisher standardmäßig durch die Options-API gemacht wird, kann der Code nun nach Funktionalitäten gebündelt geschrieben werden.
Das heißt: Dank der neuen Composition API können wir jetzt sich wiederholende Code-Fragmente aus einer Komponente herausnehmen und diese als wiederverwendbaren Code in anderen Komponenten zur Verfügung zu stellen. Allein dadurch profitieren Entwickler bereits von einer besseren Wartung und Flexibilität.
Warum brauchen wir die Composition API?
Der Aufbau einer Vue Komponente sieht in der Regel wie folgt aus:
export default {
name: 'MyComponent',
data: ...,
computed: ...,
methods: ...,
watch: ...,
...
}
Meistens reicht es schon, wenn man Funktionalitäten mit Komponenten-Optionen (data
, computed
, methods
, watch
) organisiert.
Wenn die Liste der abzubildenden Funktionalitäten oder fachlichen Anforderungen in einer Komponente wächst, werden Komponenten unweigerlich größer.
Das führt zu Komponenten, die schwer lesbar und verständlich sind – vor allem für Entwickler, die den ursprünglichen Code nicht selbst geschrieben haben.
export default {
data() {
return {
// Feature A
// Feature B
}
},
methods: {
// Feature B
/*
Feature A
*/
},
computed: {
/*
Feature B
Feature B
Feature B
*/
},
watch: {
// Feature A
}
}
Durch die Fragmentierung, die sich an den verschiedenen Bestandteilen eines Frameworks orientiert, wird es schwierig, eine komplexe Komponente zu verstehen und zu warten. Zusätzlich muss der Entwickler, wenn er an einer einzelnen fachlichen Anforderung einer Komponente arbeitet, von einem Optionen-Block zum anderen springen, um den relevanten Code zu finden.
Die Idee: Statt Funktionalitäten über das gesamte Objekt und dessen Optionen zu verteilen, wodurch wir die Übersicht verlieren, könnten wir einzelne, logisch zusammenhängende Code-Teile an einer Stelle verorten und damit sogar über Komponenten hinweg teilen.
Und das ist genau das, was die Composition API uns ermöglicht!
Im linken Teil der Grafik kann man gut erkennen, wie eine große Komponente mit verschiedenen Features (farblich markiert) aussehen kann. Rechts sehen wir, wie die optimierte Struktur mittels Composition API schließlich aufgebaut ist.
Code-Beispiel
Anhand eines Beispiels wollen wir uns mit den Neuerungen der Composition API vertraut machen und die einzelnen Zusammenhänge besser verstehen. Dazu schauen wir uns zunächst eine Komponente an, die mit der Options-API geschrieben ist und mehrere Aufgaben erledigt:
- Die Anfrage an eine externe API, um passende Bilder zu einem Tag zu erhalten. Das Tag wird über eine
prop
an die Komponente übergeben. Beim Ändern des Tags sollte der Request erneut ausgeführt werden. - Die Bilder sollen über einen Suchstring
searchQuery
durchsucht werden können. - Das Filtern von Bilder mittels eines
filters
Objekts.
export default {
props: {
tag: {
type: String,
required: true,
},
},
data() {
return {
images: [],
searchQuery: '',
filters: { ... },
};
},
methods: {
fetchImages() { ... },
updateFilters() { ... },
},
computed: {
filteredImages() { ... },
searchedImages() { ... },
},
watch: {
tag: 'fetchImages',
},
mounted() {
this.fetchImages();
},
}
Unsere Vue Komponente bekommt hierfür, wie üblich, ein Options-Objekt, das die verschiedenen Eigenschaften der Komponente beschreibt. Die Eigenschaften des Objekts sind jeweils für einen bestimmten Komponenten-Bereich verantwortlich.
Als prop
erhalten wir mit tag
das Tag, zu dem wir die Bilder anfragen wollen.
Zur Speicherung von Daten innerhalb unserer Komponente benötigen wir das Property data
. Darin befindet sich ein Array aller Bilder (images
), das anfangs leer ist.
Die Daten hierzu sollen erst beim Erstellen der Komponente über eine externe Schnittstelle angefragt werden.
Hierzu benötigen wir den Lifecycle-Hook mounted
. Dieser steht innerhalb des Options-Objekts als Methode bereit.
Sobald die Komponente erstellt und in den DOM geladen (mounted) worden ist, wird auch der darin befindliche Code ausgeführt. Mehr dazu findet ihr unter Lifecycle-Hooks.
Über ein Eingabefeld soll der User die Bilder durchsuchen können, dafür verwenden wir die Eigenschaft searchQuery
.
Um die gefilterte Liste dem Nutzer anzuzeigen, nutzen wir eine computed property
, die unter filteredImages
zu finden ist.
Dadurch erreichen wir, dass die Liste jedes Mal neu berechnet wird, sobald sich einer ihrer Abhängigkeiten (filter
, images
) verändert.
Das Options-Objekt kann aber noch ganz andere Eigenschaften beinhalten. Für eine vollständige Liste empfehlen wir diesen Link.
Nutzung der Composition API
Wichtig: Die Composition API wird standardmäßig mit Vue.js 3 ausgeliefert. Für Vue.js 2 ist sie als Plugin erhältlich.
Um mit der Composition API arbeiten zu können, benötigen wir als Erstes einen Platz dafür.
In einer Vue Komponente wird dieser Bereich als setup
bezeichnet. Diese Option stand dem Konfigurationsobjekt von Komponenten vorher nicht zur Verfügung und wird somit um diese erweitert.
Das Besondere: Die neue setup
Komponenten-Option wird ausgeführt, bevor die Komponente erstellt wird. Genauer noch umschließt setup
den Aufruf von created
und beforeCreate
. Insofern sollte jeglicher Code, der sonst innerhalb dieser beiden Lifecycle-Hooks stehen würde, direkt in die setup
-Methode geschrieben werden.
Wenn Ihr mehr über die setup
Komponenten-Option erfahren möchtet, schaut Euch am besten dieses Video an.
Doch Vorsicht: Da die Komponenten-Instanz noch nicht erstellt, aber setup
ausgeführt wurde, gibt es keinen Zugriff auf this
in der setup
Option. Und das bedeutet wiederum, dass (mit Ausnahme von props
) Ihr keinen Zugang zu Optionen habt, die in der Komponente angegeben wurden, z. B. state (data
), computed properties
oder methods
.
Der Zugriff auf props
ist natürlich essenziell, weshalb wir diese als ersten Parameter in unserer setup
Funktion hereinbekommen. Ihr möchtet mehr über props und context beim setup erfahren?
Das Teilen von Daten zwischen der Composition API und der restlichen Komponente
Da setup
eine gewöhnliche Funktion ist, können wir hier natürlich auch das return
Statement verwenden, um somit Dinge zurückgeben zu lassen. Alles, was wir per return
aus dem setup
zurückgeben, wird an die restlichen Komponentenoptionen weitergegeben. Das heißt also, wir können setup
neben unseren anderen Optionen verwenden und in diesen auch auf Eigenschaften aus setup
zugreifen.
Außerdem stehen uns sämtliche zurückgegebene Eigenschaften auch im template
-Teil der Komponente zur Verfügung.
Hier seht Ihr beispielhaft, wie setup zur Komponente hinzugefügt wird:
export default {
props: {
tag: {
type: String,
required: true,
},
},
setup(props) {
console.log(props); // { tag: '' }
return {}; // Alles, was hier zurückgegeben wird, steht dem Rest der Komponente zur Verfügung
},
// Weitere Optionen möglich
}
Extraktion des ersten Features
Wir beginnen mit dem ersten logischen Teil und möchten die Bilder zu einem Tag von einer externen API holen.
- Die Anfrage an eine externe API, um passende Bilder zu einem Tag zu erhalten. Das Tag wird über eine `prop an die Komponente übergeben. Beim Ändern des Tags sollte der Request erneut ausgeführt werden.
Hierfür benötigen wir:
- Eine Möglichkeit die Bilder zu speichern
- Die Funktion, um die Bilder vom Server abzurufen
- Die Rückgabe sowohl der Liste als auch der Funktion, damit andere Komponenten-Optionen und das Template darauf zugreifen können
Das könnte folgendermaßen aussehen:
import fetchImagesFromAPI from '@/api/fetch-images';
export default {
props: {
tag: {
type: String,
required: true,
},
},
setup(props) {
let images = [];
async function fetchImages() {
images = await fetchImagesFromAPI(props.tag);
}
return {
images,
fetchImages,
};
},
}
Leider funktioniert das noch nicht, da unsere images
Variable nicht reaktiv ist.
Die Folge: Vue bekommt Änderungen, z.B. unsere spätere Zuweisung innerhalb von fetchImages
, nicht mit und die Komponente wird nicht neu gerendert.
Im Vue 2 Kontext haben wir unsere Variablen in data
definiert und damit genau diese Reaktivität automatisch und nahezu magisch ohne weiteres Zutun erhalten.
Um in Vue.js 3 eine Variable reaktiv zu machen, benötigen wir mit der Composition API eine neue Funktion namens ref
.
Reaktivität in Vue 3
Die ref
Funktion ist sicherlich eine der wichtigsten Elemente in der Composition API. Dank dieser kann eine Variable reaktiv gemacht werden.
import { ref } from 'vue';
const count = ref(0);
ref
übernimmt das Argument und gibt es in einem Objekt mit einer value
Property zurück.
Dieses kann dann genutzt werden, um Zugriff auf den Wert der reaktiven Variablen zu erlangen oder diese zu verändern.
import { ref } from 'vue';
const count = ref(0);
console.log(count.value); // 0
counter.value += 1;
console.log(counter.value); // 1
Das wirkt auf den ersten Blick recht umständlich und gewöhnungsbedürftig.
Das Einschließen von Werten in einem Objekt ist allerdings essenziell, um ein einheitliches Verhalten über verschiedene Datentypen in JavaScript zu gewährleisten.
Der Grund: In JavaScript werden primitive Datentypen wie Number
oder String
per Kopie weitergegeben und nicht durch die Referenz.
Hierzu eine hervorragende Veranschaulichung von penjee.com:
In JavaScript existieren neben den primitiven Datentypen auch sogenannte strukturelle Typen. Dazu gehört auch object
, welches wir am häufigsten durch die Syntax {}
verwenden. Dieser Datentyp wird entgegen der Funktionsweise von primitiven Datentypen per Referenz übergeben und nicht als Kopie. Als weiterführende Literatur hierzu empfehlen wir zum Beispiel diesen Link.
Durch eine Weitergabe per Referenz lassen sich Objekte über Funktionsaufrufe mitnehmen und weiterverarbeiten (siehe oben).
Diese Funktionalität macht sich Vue.js zunutze, um auch primitive Typen reaktiv zu bekommen. Ein primitiver Typ wird also in einem Objekt eingeschlossen und wird somit von nun an auch per Referenz weitergegeben.
Nutzung von ref in unserem Beispiel
Als Nächstes erstellen wir also eine reaktive images
Variable:
import { ref } from 'vue';
import fetchImagesFromAPI from '@/api/fetch-images';
export default {
props: {
tag: {
type: String,
required: true,
},
},
setup(props) {
let images = ref([]);
async function fetchImages() {
images.value = await fetchImagesFromAPI(props.tag);
}
return {
images,
fetchImages,
};
},
}
Sobald wir fetchImages
aufrufen, wird das images
Array verändert und das Komponenten-Template neu gerendert, um die Anpassungen anzuzeigen.
Insgesamt sollte unsere Komponente nun so aussehen:
import { ref } from 'vue';
import fetchImagesFromAPI from '@/api/fetch-images';
export default {
props: {
tag: {
type: String,
required: true,
},
},
setup(props) {
let images = ref([]);
async function fetchImages() {
images.value = await fetchImagesFromAPI(props.tag);
}
return {
images,
fetchImages,
};
},
data() {
return {
searchQuery: '',
filters: { ... },
};
},
methods: {
updateFilters() { ... },
},
computed: {
filteredImages() { ... },
searchedImages() { ... },
},
watch: {
tag: 'fetchImages',
},
mounted() {
this.fetchImages();
},
}
Ihr könnt jetzt sehen, wie wir verschiedene Elemente unseres ersten Features in die setup
Methode verschoben haben.
Was noch fehlt: Wir rufen die fetchImages
in der mounted
Lebenszyklus-Methode auf und stellen einen Watcher bereit, der sich darum kümmern soll neue Bilder anzufragen, sobald sich die tag
prop verändert.
Wir hoffen, dass Euch unsere kleine Einführung etwas helfen konnte. Im nächsten Teil geht es weiter mit dem Lifecycle Hook.
Inhaltsverzeichnis
Um alle Neuigkeiten zu erfahren, abonniere hier unseren Newsletter!
Newsletter abonnierenAntony Konstantinidis