useDeferredValue gibt dir eine Kopie eines Werts, die hinterherhinken darf. Wenn sich der Wert ändert, zeichnet React den Bildschirm zuerst mit der alten Kopie neu, was das Tippen sofort hält, und rendert dann den langsamen Teil im Hintergrund mit dem neuen Wert.
Tippe schnell ein Wort. Das Input kommt mit, und die Liste wird blass, solange sie hinterherhinkt. Die Konsole loggt eine Zeile SlowList rendering pro Buchstabe, weil SlowList loggt, sobald React beginnt, es zu rendern, aber die Listen für die Zwischenwerte erreichen nie den Bildschirm: Jeder neue Buchstabe hat den Render im Hintergrund unterbrochen, und React hat mit dem neuesten Text von vorn begonnen. Ändere query={deferredQuery} zu query={query} und tippe erneut: Jeder Tastendruck wartet jetzt eine Viertelsekunde auf die Liste.
Die Syntax
const deferredValue = useDeferredValue(value);
const deferredValue = useDeferredValue(value, initialValue); // React 19
valueist alles, was du renderst: ein String aus einem Input, eine Zahl, ein Objekt. Primitive funktionieren am besten, weil ein neues Objekt bei jedem Render immer als Änderung zählt.- Der Rückgabewert ist meistens gleich
value. Direkt nachdem sichvaluegeändert hat, enthält er für einen Render noch den vorherigen Wert und holt in einem Render im Hintergrund auf.
Wie es funktioniert
Jede Änderung erzeugt zwei Renders:
- Einen dringenden Render mit dem neuen
valueund dem altendeferredValue. Das Input zeigt den neuen Text. Komponenten, die nurdeferredValuebekommen, erhalten dieselben Props wie vorher. - Einen Render im Hintergrund mit dem neuen
deferredValue. Das ist der langsame. React rendert ihn in Stücken und verwirft ihn, wenn sichvalueerneut ändert, bevor er fertig ist, und beginnt dann mit dem neuesten Wert von vorn.
Du kannst das beobachten, indem du beide Werte loggst:
Tippe einen einzelnen Buchstaben, etwa a, und die Konsole gibt zwei weitere Zeilen aus: zuerst text = "a" mit deferred = "", dann beide "a". Dieser erste Render hält das Tippen schnell, aber nur, wenn der langsame Teil ihn überspringen kann. Deshalb ist SlowList im ersten Beispiel in memo gepackt: Beim dringenden Render hat sich seine Prop query nicht geändert, also überspringt React es. Ohne memo würde der dringende Render trotzdem die ganze langsame Liste ausführen, nur mit dem alten Text, und du gewännest nichts.
Veraltete Inhalte zeigen
Solange der Render im Hintergrund läuft, unterscheiden sich value und der verzögerte Wert. Vergleiche sie, um zu zeigen, dass der Inhalt veraltet ist, ohne ihn zu verstecken:
const isStale = query !== deferredQuery;
<div style={{ opacity: isStale ? 0.5 : 1, transition: 'opacity 0.2s' }}>
<SlowList query={deferredQuery} />
</div>
Das ist meist angenehmer als ein Spinner: Die alten Ergebnisse bleiben lesbar und ändern sich an Ort und Stelle, wenn die neuen bereit sind. Dieselbe Idee funktioniert mit Suspense: Wenn der verzögerte Wert eine Komponente beim Laden von Daten suspendieren lässt, zeigt React weiter den alten Inhalt statt des Fallbacks, bis der neue Inhalt bereit ist (siehe Suspense und lazy).
Der Anfangswert (React 19)
Standardmäßig gibt es beim ersten Render nichts, dem man hinterherhinken könnte, also ist der verzögerte Wert von Anfang an der echte Wert. Seit React 19 kannst du ein zweites Argument übergeben. Beim ersten Render gibt der Hook initialValue zurück, und React rendert dann im Hintergrund erneut mit dem echten Wert. So kannst du sofort etwas Billiges zeigen und den teuren Teil direkt danach füllen.
Die Konsole zeigt zwei Renders: zuerst mit "", dann mit dem vollen Text. Entferne das zweite Argument, und nur ein Render wird geloggt, gleich mit dem vollen Text.
useDeferredValue vs. useTransition
Beide markieren Arbeit als nicht dringend und nutzen dieselbe Render-Maschinerie. Der Unterschied ist, wo du sie einsetzt:
- useTransition umhüllt das Update. Du rufst
startTransition(() => setQuery(text))auf, brauchst also den Setter.isPendingbekommst du gratis dazu. - useDeferredValue umhüllt den Wert. Du rufst es dort auf, wo der Wert genutzt wird, also funktioniert es, wenn der Wert als Prop ankommt oder aus einem Hook stammt, den du nicht kontrollierst. „Pending“ berechnest du selbst mit
value !== deferredValue.
// You own the state: either works, a transition is direct
startTransition(() => setQuery(nextQuery));
// You only receive the value
function SearchResults({ query }) {
const deferredQuery = useDeferredValue(query);
return <SlowList query={deferredQuery} />;
}
useDeferredValue vs. Debounce und Throttle
Debouncing wartet nach der letzten Änderung eine feste Zeit; Throttling aktualisiert höchstens einmal pro Intervall. Beide legen vorab eine Verzögerung fest, also machen sie die Seite auf einem schnellen Laptop langsamer als nötig, und auf einem langsamen Handy ist die Verzögerung womöglich noch zu kurz.
useDeferredValue hat keine Verzögerung. Der Render im Hintergrund startet sofort, also aktualisiert sich die Liste auf einem schnellen Gerät fast sofort, und auf einem langsamen hält React das Input reaktionsfähig und rendert die Liste so oft es kann. Die Arbeit im Hintergrund ist außerdem unterbrechbar, ein gedebounceter Render nicht: Sobald er startet, blockiert er, bis er fertig ist.
Was es nicht tut, ist Netzwerkanfragen zu reduzieren. Wenn jeder neue Wert einen Fetch startet, ändert sich der verzögerte Wert trotzdem mit jedem fertigen Render, also debounce die Anfrage selbst, wenn die Menge der Anfragen wichtig ist.
Typische Fehler
memovergessen. Wie oben gezeigt, muss die langsame Komponente den dringenden Render überspringen können. Wenn sie nicht inmemogepackt ist oder eine ihrer anderen Props bei jedem Render ein neues Objekt ist, rendert sie zweimal pro Tastendruck statt einmal.- Ein neues Objekt übergeben.
useDeferredValue({ query })erzeugt bei jedem Render ein neues Objekt, also sieht der verzögerte Wert immer geändert aus, und React startet jedes Mal einen zusätzlichen Render im Hintergrund. Verzögere das Primitive (useDeferredValue(query)) und bau Objekte daraus. - Weniger Anfragen erwarten. Eine Komponente, die auf Basis des verzögerten Werts Daten lädt, lädt trotzdem für jeden Wert, der fertig gerendert wird.
Wann du dazu greifst
Nutze useDeferredValue, wenn ein Input einen Teil der Seite steuert, der langsam rendert, der langsame Teil memoisiert werden kann und es in Ordnung ist, für einen Moment leicht veraltete Ergebnisse zu zeigen: Suchergebnisse, ein Diagramm, das sich aus einem Filter neu zeichnet, eine Vorschau von formatiertem Text. Wenn der langsame Teil aus einem behebbaren Grund langsam ist, etwa weil er 5.000 Zeilen rendert, obwohl 50 sichtbar sind, behebe das zuerst. Für die Version dieses Musters, bei der dir der Setter gehört, siehe useTransition.
Häufig gestellte Fragen
Was macht useDeferredValue?
Es gibt eine Version eines Werts zurück, die dem echten hinterherhinken darf. Wenn sich der Wert ändert, rendert React zuerst mit dem alten verzögerten Wert und dann im Hintergrund erneut mit dem neuen, und dieser Render im Hintergrund kann unterbrochen werden.
Warum macht useDeferredValue meine Liste nicht schneller?
Die langsame Komponente muss in memo gepackt sein und den verzögerten Wert bekommen. Sonst rendert der dringende Render die langsame Komponente trotzdem, nur mit dem alten Wert, und das Tippen bleibt langsam.
Was ist der Unterschied zwischen useDeferredValue und useTransition?
useTransition umhüllt das State-Update, also brauchst du Zugriff auf den Setter. useDeferredValue umhüllt einen Wert, also funktioniert es, wenn du nur den Wert bekommst, etwa als Prop oder aus einem Hook, den du nicht kontrollierst.
Ist useDeferredValue ein Debounce?
Nein. Es gibt keine feste Verzögerung. React startet den verzögerten Render direkt nach dem dringenden, und auf einem schnellen Gerät ist die Verzögerung kaum sichtbar. Es senkt außerdem allein nicht die Zahl der Netzwerkanfragen.
Was ist das zweite Argument von useDeferredValue?
Seit React 19 kannst du einen initialValue übergeben. Beim ersten Render gibt der Hook ihn zurück, dann rendert React im Hintergrund erneut mit dem echten Wert.