Menu

React useDeferredValue: niech wolne części zostaną w tyle

useDeferredValue daje ci kopię wartości, która może zostać w tyle przy ciężkich renderowaniach, więc pole tekstowe pozostaje responsywne, a wolna lista nadrabia zaległości. Dowiedz się, jak to działa, poznaj wartość początkową z React 19 i porównanie z useTransition i debouncingiem.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

useDeferredValue daje ci kopię wartości, która może zostawać w tyle. Gdy wartość się zmienia, React najpierw przerysowuje ekran ze starą kopią, dzięki czemu pisanie jest natychmiastowe, a potem w tle renderuje wolną część z nową wartością.

Szybko wpisz słowo. Pole nadąża, a lista przygasa, gdy zostaje w tyle. Konsola wypisuje jedną linię SlowList rendering na literę, bo SlowList wypisuje log, gdy tylko React zacznie go renderować, ale listy dla wartości pośrednich nigdy nie trafiają na ekran: każda nowa litera przerywała renderowanie w tle, a React zaczynał od nowa z najnowszym tekstem. Zmień query={deferredQuery} na query={query} i pisz ponownie: każde naciśnięcie klawisza czeka teraz ćwierć sekundy na listę.

Składnia

const deferredValue = useDeferredValue(value);
const deferredValue = useDeferredValue(value, initialValue); // React 19
  • value to cokolwiek, co renderujesz: tekst z pola, liczba, obiekt. Najlepiej działają wartości prymitywne, bo nowy obiekt przy każdym renderowaniu zawsze liczy się jako zmiana.
  • Zwracana wartość przez większość czasu równa się value. Zaraz po zmianie value przez jedno renderowanie nadal trzyma poprzednią wartość i dogania ją w renderowaniu w tle.

Jak to działa

Każda zmiana daje dwa renderowania:

  1. Pilne renderowanie z nowym value i starym deferredValue. Pole pokazuje nowy tekst. Komponenty, które dostają tylko deferredValue, dostają te same propsy co wcześniej.
  2. Renderowanie w tle z nowym deferredValue. To jest to wolne. React renderuje je w kawałkach i porzuca, jeśli value znowu się zmieni, zanim się skończy, a potem zaczyna od nowa z najnowszą wartością.

Możesz to zobaczyć, wypisując obie wartości:

Wpisz jedną literę, na przykład a, a konsola wypisze dwie kolejne linie: najpierw text = "a" z deferred = "", a potem obie "a". To pierwsze renderowanie sprawia, że pisanie jest szybkie, ale tylko wtedy, gdy wolna część może je pominąć. Dlatego SlowList w pierwszym przykładzie jest owinięty w memo: podczas pilnego renderowania jego prop query się nie zmienił, więc React go pomija. Bez memo pilne renderowanie nadal uruchamiałoby całą wolną listę, tylko ze starym tekstem, i nic byś nie zyskał.

Pokazywanie nieaktualnej treści

Podczas renderowania w tle value i odroczona wartość się różnią. Porównaj je, żeby powiedzieć użytkownikowi, że treść jest nieaktualna, bez jej ukrywania:

const isStale = query !== deferredQuery;

<div style={{ opacity: isStale ? 0.5 : 1, transition: 'opacity 0.2s' }}>
    <SlowList query={deferredQuery} />
</div>

Zwykle jest to przyjemniejsze niż spinner: stare wyniki pozostają czytelne i zmieniają się na miejscu, gdy nowe są gotowe. Ten sam pomysł działa z Suspense: jeśli odroczona wartość sprawi, że komponent zawiesi się w trakcie ładowania danych, React dalej pokazuje starą treść zamiast widoku zastępczego, dopóki nowa nie będzie gotowa (zobacz Suspense i lazy).

Wartość początkowa (React 19)

Domyślnie pierwsze renderowanie nie ma za czym zostawać w tyle, więc odroczona wartość od początku jest prawdziwą wartością. Od React 19 możesz przekazać drugi argument. Przy pierwszym renderowaniu hook zwraca initialValue, a potem React renderuje ponownie w tle z prawdziwą wartością. Pozwala to od razu pokazać coś taniego, a kosztowną część uzupełnić zaraz potem.

Konsola pokazuje dwa renderowania: najpierw z "", potem z pełnym tekstem. Usuń drugi argument, a wypisze się tylko jedno renderowanie, od razu z pełnym tekstem.

useDeferredValue a useTransition

Oba oznaczają pracę jako niepilną i używają tego samego mechanizmu renderowania. Różnica polega na tym, gdzie je umieszczasz:

  • useTransition owija aktualizację. Wywołujesz startTransition(() => setQuery(text)), więc potrzebujesz settera. isPending dostajesz za darmo.
  • useDeferredValue owija wartość. Wywołujesz go tam, gdzie wartość jest używana, więc działa, gdy wartość przychodzi jako prop albo pochodzi z hooka, którego nie kontrolujesz. „Oczekiwanie" wyliczasz sam przez 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 a debounce i throttle

Debouncing czeka określony czas po ostatniej zmianie; throttling aktualizuje najwyżej raz na interwał. Oba wybierają opóźnienie z góry, więc na szybkim laptopie spowalniają stronę bardziej, niż trzeba, a na wolnym telefonie opóźnienie wciąż może być za krótkie.

useDeferredValue nie ma opóźnienia. Renderowanie w tle zaczyna się od razu, więc na szybkim urządzeniu lista aktualizuje się prawie natychmiast, a na wolnym React utrzymuje responsywność pola i renderuje listę tak często, jak potrafi. Praca w tle da się też przerwać, czego nie da się zrobić z renderowaniem po debounce: gdy już się zacznie, blokuje, dopóki się nie skończy.

Nie zmniejsza natomiast liczby żądań sieciowych. Jeśli każda nowa wartość uruchamia fetch, odroczona wartość nadal zmienia się przy każdym renderowaniu, które się zakończy, więc gdy liczba żądań ma znaczenie, zastosuj debounce do samego żądania.

Częste błędy

  • Zapomnienie o memo. Jak pokazano wyżej, wolny komponent musi móc pominąć pilne renderowanie. Jeśli nie jest owinięty w memo albo któryś z jego pozostałych propsów to nowy obiekt przy każdym renderowaniu, renderuje się dwa razy na naciśnięcie klawisza zamiast raz.
  • Przekazywanie nowego obiektu. useDeferredValue({ query }) tworzy nowy obiekt przy każdym renderowaniu, więc odroczona wartość zawsze wygląda na zmienioną, a React za każdym razem zaczyna dodatkowe renderowanie w tle. Odraczaj wartość prymitywną (useDeferredValue(query)) i z niej buduj obiekty.
  • Oczekiwanie mniejszej liczby żądań. Komponent, który pobiera dane na podstawie odroczonej wartości, nadal pobiera je dla każdej wartości, której renderowanie się zakończy.

Kiedy po niego sięgnąć

Używaj useDeferredValue, gdy jedno pole steruje częścią strony, która renderuje się wolno, wolną część da się zmemoizować, a tobie nie przeszkadza przez chwilę pokazywanie trochę starych wyników: wyniki wyszukiwania, wykres przerysowywany z filtra, podgląd sformatowanego tekstu. Jeśli wolna część jest wolna z powodu, który da się naprawić, na przykład renderuje 5000 wierszy, gdy widocznych jest 50, najpierw to napraw. Wersję tego wzorca, w której jesteś właścicielem settera, opisuje useTransition.

Najczęściej zadawane pytania

Co robi useDeferredValue?

Zwraca wersję wartości, która może zostawać w tyle za prawdziwą. Gdy wartość się zmienia, React najpierw renderuje ze starą odroczoną wartością, a potem renderuje ponownie w tle z nową, a to renderowanie w tle można przerwać.

Dlaczego useDeferredValue nie przyspiesza mojej listy?

Wolny komponent musi być owinięty w memo i dostawać odroczoną wartość. Inaczej pilne renderowanie i tak renderuje wolny komponent, tylko ze starą wartością, a pisanie dalej jest wolne.

Czym różni się useDeferredValue od useTransition?

useTransition owija aktualizację stanu, więc potrzebujesz dostępu do settera. useDeferredValue owija wartość, więc działa wtedy, gdy tylko dostajesz wartość, na przykład jako prop albo z hooka, którego nie kontrolujesz.

Czy useDeferredValue to debounce?

Nie. Nie ma stałego opóźnienia. React zaczyna odroczone renderowanie zaraz po pilnym, a na szybkim urządzeniu opóźnienie jest ledwo widoczne. Samo w sobie nie zmniejsza też liczby żądań sieciowych.

Jaki jest drugi argument useDeferredValue?

Od React 19 możesz przekazać initialValue. Przy pierwszym renderowaniu hook zwraca tę wartość, a potem React renderuje ponownie w tle z prawdziwą wartością.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ