Concurrent Features in React: useTransition und useDeferredValue
Concurrent Features: useTransition und useDeferredValue
~14 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
React 18 führte ein neues Konzept ein: Concurrent Rendering – React kann ein begonnenes Rendern UNTERBRECHEN, um eine dringendere Aktualisierung (z. B. Tastatureingabe) zwischendurch zu verarbeiten, und danach fortsetzen. useTransition und useDeferredValue sind die zwei Hooks, mit denen SIE React mitteilen, welche Updates "dringend" und welche "kann warten" sind.
Das Szenario: eine teure Suche
Unsere Produktsuche filtert bisher nur 6 Produkte einer Seite – blitzschnell, kein Problem. Stellen Sie sich aber vor, die Suche würde (wie in vielen echten Apps) durch eine GROSSE, clientseitig gehaltene Liste filtern, z. B. 5.000 Einträge mit einer aufwendigen Markierung der Treffer. Ohne Concurrent Features würde JEDER Tastendruck das gesamte UI kurz "einfrieren", bis die Filterung fertig ist – das Eingabefeld selbst würde sich verzögert anfühlen, obwohl NUR die Ergebnisliste die teure Arbeit macht.
Eine simulierte teure Liste zum Ausprobieren
Um das Problem UND die Lösung konkret zu erleben, bauen wir eine neue, eigenständige Demo-Seite SearchDemoPage mit einer künstlich verlangsamten Filterfunktion – unabhängig von ProductListPage, rein zu Lernzwecken:
import { useState } from 'react';
const ITEMS = Array.from({ length: 5000 }, (_, i) => `Artikel Nr. ${i + 1}`);
// Künstlich verlangsamt, um den Effekt spürbar zu machen -
// in einer echten App wäre die Verlangsamung ein Nebeneffekt echter Arbeit
// (komplexes Rendering, große DOM-Diffs), nicht ein bewusster Slowdown wie hier.
function filterItemsSlowly(query) {
const gefiltert = ITEMS.filter((item) => item.toLowerCase().includes(query.toLowerCase()));
const start = performance.now();
while (performance.now() - start < 200) {
// absichtliche Blockade, simuliert 200ms teure Rendering-Arbeit
}
return gefiltert;
}
function SearchDemoPage() {
const [query, setQuery] = useState('');
const results = filterItemsSlowly(query);
return (
<div>
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="5.000 Artikel durchsuchen..."
/>
<p>{results.length} Treffer</p>
<ul>
{results.slice(0, 50).map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}
export default SearchDemoPage;Fügen Sie in App.jsx eine neue Route /search-demo hinzu, die auf SearchDemoPage zeigt (analog zu den anderen <Route>-Einträgen). Tippen Sie im Browser schnell in das Suchfeld – JEDES Zeichen fühlt sich spürbar TRÄGE an, das Eingabefeld "hängt" hinter Ihrer Tipp-Geschwindigkeit her.
Die Lösung Teil 1: useTransition
useTransition markiert ein State-Update als "NICHT dringend" – React darf es unterbrechen, verzögern, oder bei schnell aufeinanderfolgenden Updates sogar überspringen, um dringendere Arbeit (Tastatureingabe rendern) zuerst zu erledigen:
import { useState, useTransition } from 'react';
const ITEMS = Array.from({ length: 5000 }, (_, i) => `Artikel Nr. ${i + 1}`);
function filterItemsSlowly(query) {
const gefiltert = ITEMS.filter((item) => item.toLowerCase().includes(query.toLowerCase()));
const start = performance.now();
while (performance.now() - start < 200) {}
return gefiltert;
}
function SearchDemoPage() {
const [inputValue, setInputValue] = useState('');
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const results = filterItemsSlowly(query);
function handleChange(event) {
const value = event.target.value;
setInputValue(value); // dringend: das Eingabefeld muss SOFORT reagieren
startTransition(() => {
setQuery(value); // nicht dringend: darf verzögert/unterbrochen werden
});
}
return (
<div>
<input
value={inputValue}
onChange={handleChange}
placeholder="5.000 Artikel durchsuchen..."
/>
{isPending && <p>Suche läuft...</p>}
<p>{results.length} Treffer</p>
<ul>
{results.slice(0, 50).map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}
export default SearchDemoPage;Der entscheidende Trick: ZWEI getrennte State-Werte. inputValue aktualisiert sich SOFORT bei jedem Tastendruck (das Eingabefeld fühlt sich immer flüssig an), query – der Wert, der die teure Filterung antreibt – wird in startTransition(...) eingepackt und darf "im Hintergrund" hinterherhinken. isPending zeigt an, ob gerade eine Transition läuft, für einen Ladehinweis.
Die Alternative: useDeferredValue
Für GENAU dieses Muster (ein Wert, der "verzögert nachziehen" darf) gibt es einen noch direkteren Hook, useDeferredValue – er erspart den zweiten State und die startTransition-Einpackung:
import { useState, useDeferredValue } from 'react';
function SearchDemoPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const results = filterItemsSlowly(deferredQuery);
const isStale = query !== deferredQuery;
return (
<div>
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="5.000 Artikel durchsuchen..."
/>
{isStale && <p>Suche läuft...</p>}
<p>{results.length} Treffer</p>
{/* ... Rest wie zuvor */}
</div>
);
}useDeferredValue(query) gibt einen "verzögerten Zwilling" von query zurück: bei einer dringenden Aktualisierung liefert es zunächst noch den ALTEN Wert weiter, und aktualisiert sich selbst erst, sobald React "Luft" dafür hat. isStale (query !== deferredQuery) ersetzt hier isPending aus dem useTransition-Ansatz.
| Hook | Wann verwenden |
|---|---|
| useTransition | Sie kontrollieren SELBST, welches set...()-Update in startTransition eingepackt wird – flexibler, mehr Code. |
| useDeferredValue | Sie "verzögern" einen BEREITS existierenden Wert (Prop oder State) – weniger Code, aber Sie brauchen die Kontrolle über den ursprünglichen setState-Aufruf nicht. |
Achtung: Concurrent Features machen die BERECHNUNG selbst NICHT schneller – die 200ms Filterarbeit dauern immer noch 200ms. Sie ändern nur die PRIORISIERUNG: dringende Updates (Tippen) werden bevorzugt behandelt, nicht-dringende (Ergebnisliste) dürfen warten. Für WIRKLICH schnellere Berechnung bräuchte es andere Werkzeuge (Web Worker, effizientere Algorithmen, Server-seitige Filterung, Virtualisierung aus dem letzten Kapitel).
Tipp: Faustregel: useTransition/useDeferredValue lohnen sich bei Interaktionen, die (a) tatsächlich spürbar Rechenzeit brauchen UND (b) mit schnelleren, unabhängigen Interaktionen (Tippen, Klicken) UM DIESELBE Render-Zeit konkurrieren. Für die meisten alltäglichen UI-Updates sind sie überflüssige Komplexität – wie beim Profiler gilt: erst MESSEN, dann entscheiden.