Zum Inhalt
wannacode

Geschwindigkeit: wann eine Komponente neu rendert

Kurz gesagt: In Vue rendert die Komponente neu, die einen geänderten Wert liest, und ein Kind, wenn seine Props neu sind; du misst zuerst, dann hält computed dasselbe Ergebnis.

Problem

Jeder Buchstabe rendert die ganze Liste

Oben gibt es ein Suchfeld und einen Button „Suchen“. Die Liste wird erst nach „Suchen“ gefiltert. ProductList schreibt bei jedem Rendern in die Konsole. Tippe „milch“ ins Feld und sieh in die Konsole.

Vue

Fünf Buchstaben, fünf neue Zeilen „rendere ProductList“. Die Liste ist dieselbe: Du hast noch nicht auf „Suchen“ geklickt. Hier gibt es acht Produkte, und du merkst es nicht. In einem echten Katalog sind es 2000, jedes mit Bild und Preis, und das Feld beginnt, deinen Fingern hinterherzuhinken.

Die Idee

Wann eine Komponente neu rendert

Eine Komponente rendert neu, wenn sich die Daten ändern, die sie zeigt. Aber was „ihre Daten“ sind, entscheiden die beiden Bibliotheken verschieden. Beide liefern das richtige Ergebnis; der Unterschied ist, wie viel Arbeit sie dafür leisten.

Vue verfolgt, welche reaktiven Werte jede Komponente beim Rendern liest. Ändert sich draft, rendert Vue nur die Komponenten neu, die draft gelesen haben: hier App. Ein Kind rendert neu, wenn seine Props sich von den vorigen unterscheiden (es vergleicht sie einzeln).

Der Zähler in ProductList.vue steckt in onBeforeMount und onBeforeUpdate: Vue ruft sie vor dem ersten und vor jedem weiteren Rendern der Komponente auf.

Hier übergibt das Template :products="visible()": eine Funktion, die filtert. Sie läuft bei jedem Rendern von App und gibt ein neues Array zurück. Für Vue ist das eine neue Prop, also rendert es auch ProductList.

Erst messen, dann reparieren. Ein Zähler in der Konsole wie hier ist die einfachste Messung. Die Vue DevTools haben eine Timeline mit den Renderings der Komponenten und ihren Zeiten. Du reparierst nur ein Rendern, das überflüssig und langsam ist.

Sieh es dir an

Sieh, wer aufleuchtet

<script setup>
const draft = ref('');
const query = ref('');
const visible = computed(() => filterByName(PRODUCTS, query.value));
</script>
<template>
<input v-model="draft" />
<button @click="query = draft">Suchen</button>
<ProductList :products="visible" @add="add" />
</template>
<!-- ProductList.vue -->
<script setup>
defineProps(['products']);
onBeforeUpdate(() => console.log('rendere ProductList'));
</script>
Schritt für Schritt, in Worten
  1. App hält den Text des Feldes (draft) und die Suche (query). ProductList zeichnet die Karten. Die Konsole zählt, wie oft die Liste rendert.
  2. Vor der Korrektur rief das Template eine Funktion auf, visible(). Jedes Rendern von App erzeugte ein neues Array, deshalb sah Vue eine neue Prop und renderte ProductList neu.
  3. computed hält dasselbe Array, solange query dasselbe ist. Vue verfolgt, was jede Komponente liest, und vergleicht die Props des Kindes.
  4. Jetzt rendert eine Taste nur App und das Feld. Die Props von ProductList sind dieselben, also wird sie übersprungen, und der Zähler bleibt.
  5. „Suchen“ ändert query. Das Array ist neu, also rendert ProductList. Dieses Rendern ist nötig: Die Liste zeigt jetzt zwei Produkte.
  6. Erst messen: ein Zähler in der Konsole oder der Profiler in den DevTools. Repariere nur die Renderings, die überflüssig und langsam sind.
Vue rendert das Kind nicht, wenn seine Props dieselben sind. Es kommt also darauf an, was du übergibst: computed hält das Array, während eine Funktion im Template bei jedem Rendern ein neues liefert.

Probier es aus

Nur „Suchen“ rendert die Liste

Sorge dafür, dass Tippen ins Feld und „Hinzufügen“ ProductList nicht noch einmal rendern. Nur „Suchen“ soll sie rendern, denn nur dann ändert sich die Liste. Die Prüfung tippt „milch“, fügt zwei Produkte hinzu, klickt auf „Suchen“ und zählt die Zeilen „rendere ProductList“ in der Konsole.

Vue

Probier es aus

Finde den Fehler

visible ist computed, und die Liste rendert trotzdem bei jedem Buchstaben. Welche Zeile ist schuld?

Wähle die Zeile mit dem Fehler.

Probier es aus

Ergänze

Wie bleibt die teure Liste zwischen den Renderings dieselbe?

const visible = (
  () => PRODUCTS.filter((p) => p.name.includes(query.value)),
);

Die Idee

Eine lange Liste: weniger rendern

Das schnellste Element ist das, das nicht gezeichnet wird. Ein Mensch sieht 10–20 Produkte auf dem Bildschirm, nicht 2000. Also zeichnet eine lange Liste nur einen Teil:

  • „Mehr anzeigen“: die ersten 20, dann bei einem Klick 20 weitere. Gemerkt wird nur, wie viele angezeigt werden.
  • Seiten: „1, 2, 3 …“, oft mit der Nummer in der Adresse (/?page=2), damit man sie teilen kann.
  • Eine virtuelle Liste (Windowing): zeichnet nur die Zeilen, die sichtbar sind, und tauscht sie beim Scrollen aus. Sie ist schwer zu schreiben, deshalb nimmst du eine Bibliothek: TanStack Virtual oder vue-virtual-scroller.

Die Schlüssel aus der Lektion über Listen helfen auch hier: Mit key={p.id} (:key="p.id") sind die 20 hinzugefügten Produkte neue Elemente, und die alten bleiben bestehen, ohne neu gemacht zu werden.

Die Idee

Lazy Loading

Nicht jeder Teil der Seite wird sofort gebraucht. Die Bewertungen eines Produkts, die Karte der Läden, der Profil-Editor: Sie können erst laden, wenn der Mensch sie öffnet. Dann lädt das erste Laden weniger Code herunter.

import { defineAsyncComponent } from 'vue';
const Reviews = defineAsyncComponent(() => import('./Reviews.vue'));

import('./Reviews.vue') ist ein Funktionsaufruf, der die Datei lädt und ein Promise zurückgibt. defineAsyncComponent macht daraus eine Komponente, die wie jede andere benutzt wird: <Reviews v-if="open" />.

Vue

Öffne die Konsole: Die Zeile aus reviews.js kommt erst nach dem Klick. Um ehrlich zu sein: In der Sandbox sind alle Dateien schon in der Seite, deshalb führt import() die Datei hier nur später aus. In einem echten Projekt legt Vite jede solche Datei getrennt ab, und der Browser lädt sie beim ersten Klick vom Server. Dasselbe gilt für Seiten: In Vue Router lädt component: () => import('./CartPage.vue') die Seite beim ersten Wechsel dorthin.

Genauer, für Neugierige

v-memo. Für ein sehr langes v-for hat Vue v-memo="[p.id === selected]": Die Zeile rendert nur neu, wenn sich die Werte im Array ändern. Die Dokumentation nennt es eine Mikrooptimierung für seltene Fälle. Ein falsches Array bedeutet ein verpasstes Update.

Zustand nach unten. Das Feld mit seinem draft kann eine eigene Komponente SearchBox sein, die search erst bei „Suchen“ nach oben sendet. Dann ändern die Buchstaben nur SearchBox.

computed mit stabilem Ergebnis. Ab Vue 3.4 löst ein computed, das denselben primitiven Wert zurückgibt (etwa true), nicht aus, was davon abhängt. Bei einem Objekt kannst du das vorige zurückgeben (computed((old) => …)), wenn der Inhalt derselbe ist.

Der Entwicklungsmodus ist langsamer. Warnungen und Prüfungen kosten Zeit. Miss die Geschwindigkeit der gebauten Version (vite build und vite preview), nicht nur in der Entwicklung.

Übung

Mehr anzeigen

Der Katalog hat 10 Produkte und zeichnet sie alle. Zeige die ersten 4, darunter „Angezeigt: 4 von 10“ und einen Button „Mehr anzeigen“, der 4 weitere hinzufügt. Sind alle angezeigt, verschwindet der Button. Die Prüfung zählt die Produkte nach jedem Klick und sieht sich den Text und den Button an.

Vue

Test

Teste dich

1. Die Produktseite ist langsam. Was tust du als Erstes?
2. App übergibt :products="visible()", wobei visible eine Funktion ist, die filtert. Du tippst in das Feld von App. Rendert ProductList?
3. Der Katalog hat 5000 Produkte, und die Seite öffnet sich langsam. Was hilft am meisten?
4. Was bringt das Lazy Loading einer Komponente (lazy / defineAsyncComponent) in einem echten Projekt?

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