useTransition pozwala oznaczyć aktualizację stanu jako niepilną. React renderuje taką aktualizację w tle i porzuca ją albo zaczyna od nowa, jeśli wydarzy się coś pilniejszego, na przykład kliknięcie albo naciśnięcie klawisza, więc strona pozostaje responsywna, a wolna część ekranu nadrabia zaległości.
Kliknij posts, a potem, zanim pojawi się lista, kliknij contact. Contact pokazuje się od razu: React wyrzucił niedokończone renderowanie postów. Teraz zmień startTransition(() => { setTab(next); }) na zwykłe setTab(next); i spróbuj tego samego: po kliknięciu posts strona zamarza na około 300 ms, a twoje kliknięcie contact czeka, aż posty będą gotowe.
Składnia
const [isPending, startTransition] = useTransition();
startTransition(() => {
setTab(next);
});
startTransitionprzyjmuje funkcję i od razu ją uruchamia. Wszystkie aktualizacje stanu w jej wnętrzu są oznaczane jako przejścia.isPendingma wartośćtrueod chwili rozpoczęcia przejścia, dopóki React nie skończy go renderować i nie zatwierdzi wyniku.
useTransition nie przyjmuje argumentów. Funkcja, którą przekazujesz, wykonuje się synchronicznie; przejście dotyczy tego, jak React renderuje wynikową aktualizację, a nie tego, kiedy wykonuje się twoja funkcja.
Co oznacza „niepilne"
React dzieli aktualizacje na dwa rodzaje. Pilne aktualizacje, takie jak pisanie, klikanie i naciskanie, muszą być widoczne natychmiast, bo inaczej strona wydaje się zepsuta. Aktualizacje przejściowe, takie jak przełączenie na nowy widok albo pokazanie przefiltrowanych wyników, mogą chwilę potrwać.
React renderuje przejście w małych kawałkach i między komponentami sprawdza, czy przyszło coś pilnego. Jeśli tak, React wstrzymuje albo porzuca przejście, obsługuje pilną aktualizację, a potem zaczyna przejście od nowa z najnowszym stanem. Podczas renderowania przejścia poprzedni ekran zostaje na miejscu; React nie pokazuje wyniku zbudowanego do połowy. Dlatego przykład wciąż pokazuje tekst about, ze wskazówką Loading obok przycisków, dopóki posty nie są gotowe.
Przerwanie następuje między komponentami. Pojedynczego komponentu, który blokuje przez 300 ms, nie da się przeciąć na pół, dlatego wolna praca powyżej jest rozłożona na 300 małych komponentów SlowPost.
Pokazywanie wskazówki oczekiwania
isPending to miejsce na wskaźnik ładowania, który nie zastępuje bieżącej treści: spinner obok przycisku, przyciemniony tekst, etykieta "Saving...". W pierwszym przykładzie pokazuje Loading... obok kart. Możesz też użyć go do stylowania treści:
<div style={{ opacity: isPending ? 0.6 : 1 }}>{content}</div>
Filtrowanie dużej listy w trakcie pisania
Nie możesz umieścić w przejściu własnej wartości pola. Kontrolowany <input> musi dostać nową wartość w tej samej pilnej aktualizacji, bo inaczej znaki giną. Trzymaj zamiast tego dwa kawałki stanu: tekst pola (pilny) i zapytanie, którego używa lista (przejście).
Szybko wpisz 123. Pole nigdy nie zostaje w tyle. Konsola nadal wypisuje jedną linię rendering results na każde naciśnięcie klawisza, bo Results wypisuje log, gdy tylko React zacznie go renderować, ale listy dla 1 i 12 nigdy nie trafiają na ekran: każde nowsze naciśnięcie przerwało trwające renderowanie, a React zaczął od nowa z najnowszym zapytaniem. Results jest celowo owinięty w memo: bez tego pilne renderowanie przy każdym naciśnięciu klawisza renderowałoby też wolną listę ze starym zapytaniem i straciłbyś całą korzyść.
startTransition bez hooka
react eksportuje też startTransition jako zwykłą funkcję. Oznacza aktualizacje jako przejścia tak samo jak hook, ale nie dostajesz isPending. Używaj jej tam, gdzie nie możesz wywołać hooka, na przykład w bibliotece danych albo routerze poza jakimkolwiek komponentem.
import { startTransition } from 'react';
function navigate(url) {
startTransition(() => {
setCurrentUrl(url);
});
}
Asynchroniczne przejścia w React 19
Od React 19 przekazywana funkcja może być async. isPending pozostaje wtedy true przez cały czas działania funkcji, łącznie z czekaniem, co jest prostym sposobem na pokazanie stanu oczekiwania podczas żądania. Udawane saveName poniżej zastępuje prawdziwe żądanie.
Zwróć uwagę na drugie startTransition wokół setSaved. React gubi przejście po await, więc aktualizację stanu po nim trzeba ponownie owinąć, żeby liczyła się jako część przejścia. Funkcję przekazaną w ten sposób React nazywa akcją; useActionState i akcje formularzy opierają się na tym samym mechanizmie.
Przejście to nie debounce
Debounce czeka określony czas po ostatnim naciśnięciu klawisza, zanim cokolwiek zrobi. Przejście nie czeka. Zaczyna renderować od razu, a jeśli przyjdzie nowsza aktualizacja, porzuca starą pracę. Na szybkim urządzeniu wynik pojawia się bez żadnego opóźnienia; na wolnym React robi tyle, ile zdąży między naciśnięciami klawiszy. Przejście nie potrafi natomiast zmniejszyć liczby żądań sieciowych: jeśli każde naciśnięcie klawisza uruchamia fetch, zastosuj debounce do fetcha.
Przejścia i Suspense
Przejścia zmieniają też zachowanie granicy Suspense. Normalnie, gdy komponent w granicy się zawiesza (ładuje się leniwy komponent albo dane odczytywane przez use), React zastępuje treść widokiem zastępczym. Jeśli aktualizacja, która spowodowała zawieszenie, była przejściem, React zamiast tego zostawia treść, która już jest na ekranie, a isPending pozostaje true, dopóki nowa treść nie będzie gotowa. Tak routery unikają migania spinnera wielkości strony przy każdej nawigacji: owijają nawigację w przejście, a stara strona zostaje widoczna, dopóki następna nie może się wyrenderować. Treść, która nigdy nie była pokazana, nadal używa widoku zastępczego, więc nowo odsłonięta granica zachowuje się jak zwykle.
Rzeczy, które nie działają
- Aktualizacje wewnątrz timeoutu.
startTransition(() => setTimeout(() => setTab(next), 100))nie oznacza aktualizacji, bo wykonuje się ona, gdystartTransitionjuż się zakończyło. Zamiast tego umieśćstartTransitionwewnątrz timeoutu. - Pojedynczy wolny komponent. React może przerywać tylko między komponentami, więc jeden komponent, który długo blokuje, nadal blokuje.
- Kontrolowanie pola. Jak wyżej, własny stan pola pozostaje pilny.
W przypadku, gdy nie jesteś właścicielem settera stanu, a tylko dostajesz wartość, na przykład prop, useDeferredValue daje to samo zachowanie.
Najczęściej zadawane pytania
Co robi useTransition?
Daje ci startTransition, które oznacza aktualizacje stanu w swoim wnętrzu jako niepilne, i isPending, które ma wartość true, dopóki ta aktualizacja się renderuje. React może przerwać niepilne renderowanie, żeby najpierw obsłużyć kliknięcia i pisanie.
Czym różni się useTransition od startTransition?
Oba oznaczają aktualizacje jako przejścia (transitions). Hook useTransition daje dodatkowo isPending do wskaźnika ładowania. Samodzielne startTransition importowane z react działa poza komponentami, ale nie ma flagi oczekiwania.
Czy mogę używać useTransition dla pola tekstowego?
Nie dla własnej wartości pola. Kontrolowane pole musi aktualizować się synchronicznie, bo inaczej pisanie się psuje. Stan pola zostaw jako pilny, a kosztowną aktualizację, która od niego zależy, umieść w przejściu albo użyj useDeferredValue.
Czy useTransition to to samo co debouncing?
Nie. Debounce czeka określony czas, zanim cokolwiek zrobi. Przejście zaczyna renderować od razu i zostaje przerwane, jeśli przyjdzie nowsza aktualizacja, więc na szybkim urządzeniu wynik pojawia się bez opóźnienia.
Czy startTransition może przyjąć funkcję async?
Tak, od React 19. isPending pozostaje true, dopóki funkcja async się nie zakończy. Aktualizacje stanu po await trzeba owinąć w kolejne wywołanie startTransition, żeby zostały oznaczone jako przejście.