Zum Inhalt
wannacode

Effekte

Kurz gesagt: Ein Effekt ist Code für Nebenwirkungen (ein Timer, eine Anfrage, der Titel des Tabs). In Vue führt onMounted ihn aus, sobald die Komponente auf der Seite ist, watch führt ihn aus, wenn sich seine Quelle ändert, und das Aufräumen stoppt, was gestartet wurde.

Problem

Das Banner ist weg, die Prüfungen nicht

Die Banitsa mit Käse ist ausverkauft. Der Kunde klickt auf „Bestand beobachten“, und unter dem Button erscheint ein Banner. Solange das Banner offen ist, prüft die Seite jede Sekunde den Bestand. Hier schreibt die Prüfung nur eine Zeile in die Konsole.

Vue

Klick auf „Bestand beobachten“, warte zwei Sekunden und klick auf „Beobachtung beenden“. Das Banner verschwindet, doch „Prüfung Nr. 3“, „Prüfung Nr. 4“ … kommen weiter in der Konsole an. Öffne und schließe das Banner noch einmal: Jetzt sind es zwei Zeilen pro Sekunde. Der Timer gehört dem Browser, nicht der Komponente. Niemand hat ihn gestoppt.

Die Idee

Das Zeichnen berechnet nur

Eine Komponente sagt, was auf dem Bildschirm stehen soll: Aus Props und Zustand berechnet sie das Markup. Das ist das Zeichnen. Die Bibliothek wiederholt es, wann sie will, und vergleicht das Ergebnis mit dem vorherigen.

Deshalb muss das Zeichnen rein sein: Es tut nichts außer das Markup zu berechnen. Ein Timer, eine Anfrage an den Server, eine Änderung von document.title, ein Abonnement von Ereignissen auf window tun etwas nach außen. Das sind Nebenwirkungen.

In <script setup> läuft der Code einmal, wenn die Komponente erstellt wird. Deshalb startet der Timer im Beispiel einmal pro Öffnen. Aber er startet, während die Komponente noch erstellt wird, und nichts stoppt ihn, wenn die Komponente verschwindet. Das Template ist das Zeichnen: Vue zeichnet es neu, sobald sich die Daten ändern, die es liest. Auch das Template soll nur beschreiben, was angezeigt wird.

Eine Nebenwirkung braucht zwei Dinge, die das Zeichnen nicht liefert: einen Moment zum Starten und einen Moment zum Stoppen.

Die Idee

Effekt und Aufräumen

Ein Effekt ist Code, den die Komponente ausführt, sobald sie auf der Seite ist. Das Aufräumen ist der Code, der stoppt, was gestartet wurde, wenn die Komponente verschwindet.

<script setup>
import { onMounted, onUnmounted } from 'vue';
const props = defineProps(['product']);
let id;
onMounted(() => {
id = setInterval(check, 1000);
});
onUnmounted(() => clearInterval(id));
</script>
  • onMounted(Funktion) sagt Vue: „Führe sie aus, wenn die Komponente auf der Seite ist.“
  • onUnmounted(Funktion) wird aufgerufen, wenn die Komponente die Seite verlässt. Das ist das Aufräumen.
  • id steht außerhalb beider Funktionen, weil beide es sehen müssen.

Das Aufräumen spiegelt den Effekt: setInterval und clearInterval, addEventListener und removeEventListener, eine Anfrage und das Abbrechen dieser Anfrage.

Probier es aus

Stoppe die Prüfungen

Der Timer in StockWatcher startet direkt in der Komponente, und niemand stoppt ihn. Verschiebe ihn in einen Effekt, der startet, wenn das Banner erscheint, und stoppe ihn, wenn das Banner verschwindet. Die Prüfung öffnet das Banner, wartet auf „Prüfung Nr. 1“, schließt es und stellt sicher, dass „Prüfung Nr. 2“ nie kommt.

Vue

Die Idee

Abhängigkeiten

Jetzt kann der Kunde ein anderes ausverkauftes Produkt wählen, während das Banner offen ist. Der Effekt prüft product. Sobald sich das Produkt ändert, muss die alte Prüfung stoppen und eine neue für das neue Produkt starten.

Dafür ist watch da. Es beobachtet eine Quelle und ruft seine Funktion auf, wenn sich die Quelle ändert:

watch(() => props.product, (product, oldProduct, onCleanup) => {
const id = setInterval(check, 1000);
onCleanup(() => clearInterval(id));
}, { immediate: true });
  • Die Quelle ist eine Funktion, die den Wert zurückgibt: () => props.product. Vue sieht, was sie liest, und weiß, wann es sie wieder aufrufen muss.
  • { immediate: true } führt die Funktion auch gleich aus, für das Startprodukt. Ohne diese Option wartet watch auf die erste Änderung.
  • onCleanup hält das Aufräumen dieses Laufs fest. Vue ruft es vor dem nächsten Lauf auf und wenn die Komponente verschwindet. Watcher aus <script setup> enden von selbst mit der Komponente.

watch ersetzt beide Funktionen aus der letzten Aufgabe: onMounted läuft einmal, watch läuft für jedes neue Produkt.

Sieh es dir an

Das Leben eines Effekts

Das Banner öffnet sich für die Banitsa, der Kunde wählt die Gurken, dann schließt er das Banner.

<script setup>
const props = defineProps(['product']);
watch(() => props.product, (product, old, onCleanup) => {
let checks = 0;
const id = setInterval(() => {
checks++;
console.log(`Prüfung Nr. ${checks}: ${product.name}`);
}, 1000);
onCleanup(() => clearInterval(id));
}, { immediate: true });
</script>
<template>
<p role="status">Wir beobachten den Bestand von {{ product.name }}</p>
</template>
Schritt für Schritt, in Worten
  1. Zeichnen: Vue baut das Markup aus dem Template. Das Template beschreibt nur, was angezeigt wird. Es startet nichts.
  2. immediate: true führt den Watcher gleich einmal aus, schon beim Erstellen der Komponente. Der Timer startet und zählt die Prüfungen.
  3. Der Kunde wählt ein anderes Produkt. Die Funktion () => props.product liefert jetzt einen anderen Wert, deshalb muss der Watcher noch einmal laufen.
  4. Vor dem neuen Lauf kommt das Aufräumen des alten: Sein Timer stoppt. Ohne das würden die alten Prüfungen neben den neuen weiterlaufen.
  5. Der Effekt läuft noch einmal mit dem neuen Produkt. Sein eigener Timer zählt ab Nr. 1.
  6. Der Kunde schließt das Banner: Die Komponente verlässt die Seite. Das letzte Aufräumen läuft, und der Timer stoppt damit.
  7. Jeder Lauf hat sein Gegenstück: Was der Effekt startet, stoppt sein Aufräumen. Vor dem nächsten Lauf und wenn die Komponente geht.

Der Effekt läuft zweimal, und das Aufräumen läuft zweimal: einmal vor dem neuen Lauf, einmal, wenn die Komponente entfernt wird. Dazwischen gibt es immer genau einen Timer.

Probier es aus

Finde den Fehler

Das Banner öffnet sich, aber statt der Prüfungen zeigt die Konsole eine Warnung und jede Sekunde TypeError: Cannot read properties of undefined (reading 'name'). Welche Zeile ist schuld?

Wähle die Zeile mit dem Fehler.

Probier es aus

Fülle die Lücke

Was gehört in die Lücke, damit der Timer stoppt, wenn die Komponente verschwindet?

let id;
onMounted(() => {
  id = setInterval(check, 1000);
});
(() => clearInterval(id));

Die Idee

Wann du keinen Effekt brauchst

Ein Effekt ist für eine Verbindung zu etwas außerhalb der Komponente: ein Timer, ein Server, der Titel des Tabs, window. Zwei Dinge sehen nach einem Effekt aus, sind aber keiner.

  • Ein Wert, der aus anderen berechnet wird. Die Summe des Warenkorbs steht nicht in einem eigenen Zustand, den ein Effekt aktualisiert. Sie wird beim Zeichnen berechnet mit computed, wie in der Lektion über abgeleiteten Zustand. Ein Effekt, der sie in einen eigenen Zustand schreibt, ist unnötige Arbeit und eine zweite Stelle, die vom Warenkorb abweichen kann.
  • Eine Reaktion auf eine Aktion. Die Bestellung wird gesendet, weil der Kunde auf „Bestellen“ geklickt hat. Das gehört in den Click-Handler, nicht in einen Effekt, der ein Flag beobachtet.

Was bleibt, ist die eigentliche Aufgabe eines Effekts: die Komponente mit der Außenwelt synchron zu halten, solange sie auf der Seite ist.

Genauer, für Neugierige

watchEffect. watchEffect((onCleanup) => { … }) startet sofort und beobachtet alles, was seine Funktion liest. Du listest die Quellen nicht auf, siehst aber auch nicht auf einen Blick, was es auslöst. watch mit expliziter Quelle ist klarer, wenn du genau wissen willst, wann es läuft.

onWatcherCleanup. Seit Vue 3.5 kann man das Aufräumen auch mit onWatcherCleanup(() => …) festhalten, importiert aus 'vue'. Es muss vor dem ersten await in der Funktion aufgerufen werden.

Wann immediate läuft. Eine Funktion mit { immediate: true } läuft, während die Komponente erstellt wird, noch bevor sie im DOM ist. Ein Timer und eine Anfrage brauchen das DOM nicht. Muss der Effekt Elemente aus dem Template anfassen, ist onMounted der richtige Ort.

Die Options API hat dasselbe: mounted, unmounted und die Option watch.

Übung

Ein anderes Produkt

Über dem Banner gibt es ein Dropdown „Produkt“ mit der Banitsa und den Gurken. Im Moment startet StockWatcher den Timer einmal, wenn er angezeigt wird. Sorge dafür, dass beim Wechsel des Produkts die alte Prüfung stoppt und eine neue ab Nr. 1 für das neue Produkt startet. Die Prüfung öffnet das Banner, wartet auf „Prüfung Nr. 1“ für die Banitsa, wählt die Gurken, wartet auf „Prüfung Nr. 1“ für sie, stellt sicher, dass „Prüfung Nr. 2“ für die Banitsa nie kommt, und schließt das Banner.

Vue

Test

Teste dich

1. Eine Komponente startet setInterval und stoppt es nie. Der Kunde öffnet und schließt das Banner dreimal. Wie viele Timer laufen?
2. Der Watcher ist watch(() => props.product, …), ohne { immediate: true }. Wann startet die erste Prüfung?
3. Wann läuft das Aufräumen eines Effekts?
4. Was ist KEINE Aufgabe für einen Effekt?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·