Menu

React useTransition Hook: Die UI reaktionsfähig halten

useTransition markiert ein State-Update als nicht dringend, damit React die Seite reaktionsfähig hält, während es den langsamen Teil im Hintergrund rendert. Lerne isPending, startTransition, async Transitions in React 19 und warum es kein Debouncing ist.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Mit useTransition markierst du ein State-Update als nicht dringend. React rendert dieses Update im Hintergrund und verwirft oder startet es neu, wenn etwas Dringenderes passiert, etwa ein Klick oder ein Tastendruck, also bleibt die Seite reaktionsfähig, während ein langsamer Teil des Bildschirms nachzieht.

Klicke auf posts und dann, bevor die Liste erscheint, auf contact. Contact erscheint sofort: React hat den halb fertigen Render der Posts weggeworfen. Ändere jetzt startTransition(() => { setTab(next); }) zu einem einfachen setTab(next); und probiere dasselbe: Die Seite friert nach dem Klick auf posts etwa 300 ms ein, und dein Klick auf contact wartet, bis die Posts fertig sind.

Die Syntax

const [isPending, startTransition] = useTransition();

startTransition(() => {
    setTab(next);
});
  • startTransition nimmt eine Funktion an und führt sie sofort aus. Alle State-Updates darin werden als Transitions markiert.
  • isPending ist true ab dem Moment, in dem du die Transition startest, bis React sie fertig gerendert und das Ergebnis committet hat.

useTransition nimmt keine Argumente. Die übergebene Funktion läuft synchron; bei der Transition geht es darum, wie React das daraus folgende Update rendert, nicht darum, wann deine Funktion läuft.

Was „nicht dringend“ bedeutet

React teilt Updates in zwei Arten. Dringende Updates wie Tippen, Klicken und Drücken müssen sich sofort zeigen, sonst wirkt die Seite kaputt. Transition-Updates wie der Wechsel zu einer neuen Ansicht oder das Anzeigen gefilterter Ergebnisse dürfen einen Moment dauern.

React rendert eine Transition in kleinen Stücken und prüft zwischen Komponenten, ob etwas Dringendes hereingekommen ist. Wenn ja, pausiert oder verwirft React die Transition, verarbeitet das dringende Update und startet die Transition dann mit dem neuesten State neu. Während eine Transition rendert, bleibt der vorherige Bildschirm stehen; React zeigt kein halb gebautes Ergebnis. Deshalb zeigt das Beispiel weiter den Text von about, mit dem Hinweis Loading neben den Buttons, bis die Posts bereit sind.

Unterbrochen wird zwischen Komponenten. Eine einzelne Komponente, die 300 ms blockiert, lässt sich nicht in der Mitte teilen, deshalb ist die langsame Arbeit oben auf 300 kleine SlowPost-Komponenten verteilt.

Einen Pending-Hinweis zeigen

isPending ist der Ort für eine Ladeanzeige, die den aktuellen Inhalt nicht ersetzt: ein Spinner neben dem Button, gedimmter Text, ein Label „Saving...“. Im ersten Beispiel zeigt es Loading... neben den Tabs. Du kannst es auch nutzen, um den Inhalt zu stylen:

<div style={{ opacity: isPending ? 0.6 : 1 }}>{content}</div>

Eine große Liste beim Tippen filtern

Den eigenen Wert eines Inputs kannst du nicht in eine Transition setzen. Ein kontrolliertes <input> muss seinen neuen Wert im selben dringenden Update bekommen, sonst gehen Zeichen verloren. Halte stattdessen zwei State-Werte: den Text des Inputs (dringend) und die Suche, die die Liste nutzt (eine Transition).

Tippe schnell 123. Das Input hängt nie. Die Konsole loggt trotzdem eine Zeile rendering results pro Tastendruck, weil Results loggt, sobald React beginnt, es zu rendern, aber die Listen für 1 und 12 erreichen nie den Bildschirm: Jeder neuere Tastendruck hat den laufenden Render unterbrochen, und React hat mit der neuesten Suche neu begonnen. Results ist absichtlich in memo gepackt: Ohne das würde der dringende Render bei jedem Tastendruck auch die langsame Liste mit der alten Suche rendern, und der Vorteil wäre weg.

startTransition ohne den Hook

react exportiert startTransition auch als einfache Funktion. Sie markiert Updates genau wie der Hook als Transitions, aber du bekommst kein isPending. Nutze sie, wo du keinen Hook aufrufen kannst, etwa in einer Datenbibliothek oder einem Router außerhalb jeder Komponente.

import { startTransition } from 'react';

function navigate(url) {
    startTransition(() => {
        setCurrentUrl(url);
    });
}

Async Transitions in React 19

Seit React 19 kann die übergebene Funktion async sein. isPending bleibt dann die ganze Zeit true, während die Funktion läuft, einschließlich des Wartens, was es zu einem einfachen Weg macht, während einer Anfrage einen Pending-Zustand zu zeigen. Das Fake-saveName unten steht für eine echte Anfrage.

Beachte das zweite startTransition um setSaved. React verliert die Transition über ein await hinweg aus den Augen, also muss ein State-Update danach erneut umhüllt werden, um als Teil der Transition zu zählen. React nennt eine so übergebene Funktion eine Action; useActionState und Form Actions bauen auf demselben Mechanismus auf.

Eine Transition ist kein Debounce

Ein Debounce wartet nach dem letzten Tastendruck eine feste Zeit, bevor er etwas tut. Eine Transition wartet nicht. Sie beginnt sofort zu rendern, und wenn ein neueres Update kommt, verwirft sie die alte Arbeit. Auf einem schnellen Gerät erscheint das Ergebnis ganz ohne Verzögerung; auf einem langsamen erledigt React zwischen den Tastendrücken so viel, wie es kann. Was eine Transition nicht kann, ist die Zahl der Netzwerkanfragen zu senken: Wenn jeder Tastendruck einen Fetch startet, debounce den Fetch.

Transitions und Suspense

Transitions ändern auch, was eine Suspense-Grenze tut. Normalerweise ersetzt React den Inhalt durch den Fallback, wenn eine Komponente in einer Grenze suspendiert (eine lazy Komponente lädt, oder mit use gelesene Daten). War das Update, das die Suspension ausgelöst hat, eine Transition, behält React stattdessen den Inhalt, der schon auf dem Bildschirm ist, und isPending bleibt true, bis der neue Inhalt bereit ist. So vermeiden Router, bei jeder Navigation einen seitengroßen Spinner aufblitzen zu lassen: Sie packen die Navigation in eine Transition, und die alte Seite bleibt sichtbar, bis die nächste rendern kann. Inhalt, der noch nie gezeigt wurde, nutzt weiterhin den Fallback, also verhält sich eine neu sichtbare Grenze wie gewohnt.

Was nicht funktioniert

  • Updates in einem Timeout. startTransition(() => setTimeout(() => setTab(next), 100)) markiert das Update nicht, weil es läuft, nachdem startTransition zurückgekehrt ist. Setze stattdessen startTransition in den Timeout.
  • Eine einzelne langsame Komponente. React kann nur zwischen Komponenten unterbrechen, also blockiert eine Komponente, die lange blockiert, weiterhin.
  • Ein Input kontrollieren. Wie oben bleibt der eigene State des Inputs dringend.

Für den Fall, dass dir der State-Setter nicht gehört und du nur einen Wert bekommst, etwa eine Prop, gibt dir useDeferredValue dasselbe Verhalten.

Häufig gestellte Fragen

Was macht useTransition?

Es gibt dir startTransition, das die State-Updates darin als nicht dringend markiert, und isPending, das true ist, solange dieses Update noch rendert. React kann einen nicht dringenden Render unterbrechen, um zuerst Klicks und Tippen zu verarbeiten.

Was ist der Unterschied zwischen useTransition und startTransition?

Beide markieren Updates als Transitions. Der Hook useTransition gibt dir zusätzlich isPending für einen Ladehinweis. Das eigenständige startTransition, importiert aus react, funktioniert außerhalb von Komponenten, hat aber kein Pending-Flag.

Kann ich useTransition für ein Texteingabefeld nutzen?

Nicht für den eigenen Wert des Inputs. Ein kontrolliertes Input muss synchron aktualisiert werden, sonst funktioniert das Tippen nicht. Halte den State des Inputs dringend und setze das teure Update, das davon abhängt, in eine Transition, oder nutze useDeferredValue.

Ist useTransition dasselbe wie Debouncing?

Nein. Ein Debounce wartet eine feste Zeit, bevor er etwas tut. Eine Transition beginnt sofort zu rendern und wird unterbrochen, wenn ein neueres Update kommt, also erscheint das Ergebnis auf einem schnellen Gerät ohne Verzögerung.

Kann startTransition eine async-Funktion annehmen?

Ja, seit React 19. isPending bleibt true, bis die async-Funktion fertig ist. State-Updates nach einem await müssen in einen weiteren Aufruf von startTransition gepackt werden, um als Transition markiert zu werden.

Illustration der Programmiersprachen bei Coddy

Lerne mit Coddy zu programmieren

LOS GEHT'S