useLayoutEffect to wersja useEffect, która uruchamia się wcześniej: zaraz po tym, jak React zaktualizował DOM, ale zanim przeglądarka odmaluje nową klatkę. Kod w nim może mierzyć elementy i zmieniać układ, a użytkownik widzi tylko końcowy wynik. Na czas działania blokuje odmalowanie, więc używaj go tylko wtedy, gdy potrzebujesz takiego momentu uruchomienia.
Efekty są zadeklarowane w kolejności: efekt, efekt układu, ale konsola przy każdym renderowaniu wypisuje najpierw efekt układu. Kolejność deklaracji nie ma znaczenia między tymi dwoma hookami; liczy się ich moment uruchomienia.
Składnia
Sygnatura jest identyczna jak w useEffect: funkcja setup, opcjonalna funkcja czyszcząca i tablica zależności.
useLayoutEffect(() => {
// runs after the DOM update, before paint
return () => {
// cleanup, before the next run and on unmount
};
}, [dependencies]);
Wszystko, co wiesz o zależnościach i czyszczeniu z useEffect, obowiązuje tutaj. Jedyna różnica to moment, w którym React go wywołuje.
Kiedy uruchamia się każdy z nich
Po renderowaniu React aktualizuje DOM, a potem robi trzy rzeczy w stałej kolejności:
- Synchronicznie uruchamia wszystkie funkcje czyszczące efektów układu i same efekty układu.
- Pozwala przeglądarce odmalować ekran. Jeśli efekt układu ustawił stan, React najpierw renderuje ponownie, wciąż przed odmalowaniem.
- Uruchamia zwykłe efekty, zwykle po odmalowaniu.
W całym drzewie każdy efekt układu uruchamia się przed jakimkolwiek zwykłym efektem, a dzieci przed rodzicami, bo rodzic może skończyć dopiero wtedy, gdy jego dzieci są na miejscu:
Konsola pokazuje najpierw oba efekty układu, Child, potem Parent, a potem oba zwykłe efekty w tej samej kolejności. key={n} sprawia, że każde kliknięcie montuje świeży Parent, więc za każdym razem dostajesz te same cztery linie. Dodaj do każdego efektu funkcję czyszczącą, która wypisuje log, i zobacz, jak stare uruchamiają się przed nowymi.
Problem migotania
Wyobraź sobie tooltip, który powinien pojawić się nad przyciskiem, chyba że nad nim nie ma miejsca, a wtedy idzie pod spód. Nie wiesz, czy jest miejsce, dopóki tooltip nie znajdzie się w DOM i nie zmierzysz jego wysokości. Renderowanie odbywa się więc w dwóch przebiegach:
- Wyrenderuj tooltip gdziekolwiek, żeby dało się go zmierzyć.
- Zmierz go, wylicz prawdziwą pozycję, ustaw stan, wyrenderuj ponownie.
Z useEffect przeglądarka może odmalować ekran między tymi przebiegami, a użytkownik zobaczy, jak tooltip przeskakuje z pierwszej pozycji do drugiej. Z useLayoutEffect drugi przebieg następuje przed odmalowaniem, więc pierwsza pozycja nigdy nie jest pokazana.
Najedź na górny przycisk: nad nim nie ma miejsca, więc tooltip pojawia się pod spodem. Najedź na dolny, a pojawi się nad nim. Konsola mówi, którą stronę wybrał pomiar. Zmień useLayoutEffect na useEffect i najedź ponownie: tooltip może teraz mignąć na górze ramki przez jedną klatkę, zanim się przesunie. Na szybkim komputerze często dzieje się to zbyt szybko, żeby to zauważyć, i właśnie dlatego ten błąd trafia na produkcję, a potem jest zgłaszany z wolniejszych telefonów.
offsetHeight, offsetTop i getBoundingClientRect() zmuszają przeglądarkę do przeliczenia układu, więc odczyt w efekcie układu daje prawdziwe wartości DOM, który React właśnie zapisał.
Mierzenie elementu
Ten sam wzorzec działa dla każdego rozmiaru, od którego zależy renderowanie: odczytaj go przez ref w efekcie układu, zapisz w stanie, renderuj z nim.
Kliknij przycisk, a linia z szerokością zaktualizuje się w tej samej klatce co tekst. Strona nigdy nie pokazuje nowego tekstu obok starej szerokości, bo drugie renderowanie następuje przed odmalowaniem. Usuń [text] z tablicy zależności, a pomiar będzie się wykonywał po każdym renderowaniu, także po tym, które wywołuje jego własne setWidth; zatrzymuje się tylko dlatego, że szerokość przestaje się zmieniać.
Wybieraj useEffect
Efekt układu wstrzymuje odmalowanie, dopóki nie wróci, a jeśli ustawia stan, także dopóki nie skończy się dodatkowe renderowanie. Wolny efekt sprawia, że każda aktualizacja na stronie wydaje się wolna. Używaj useLayoutEffect tylko wtedy, gdy wszystkie te warunki są spełnione:
- Efekt odczytuje układ (rozmiar, pozycję, przewinięcie).
- Na podstawie tego odczytu zmienia to, co jest renderowane.
- Stan pośredni byłby widoczny i wyglądałby źle.
Wszystko inne (pobieranie danych, subskrypcje, timery, logowanie, analityka) należy do useEffect. Prosta zasada: zacznij od useEffect i zmień go tylko wtedy, gdy widzisz migotanie.
useEffect zwykle uruchamia się po odmalowaniu, ale nie zawsze. Gdy aktualizacja pochodzi z dyskretnej akcji użytkownika, takiej jak kliknięcie albo naciśnięcie klawisza, React może uruchomić efekty, zanim przeglądarka odmaluje ekran. To jeden z powodów, dla których migotanie może pojawić się w jednej sytuacji, a w innej nie.
Renderowanie na serwerze
Efekty nigdy nie uruchamiają się podczas renderowania na serwerze, useLayoutEffect też nie. Serwer wysyła HTML pierwszego renderowania, a efekt układu uruchamia się dopiero po hydracji w przeglądarce. React 18 i wcześniejsze ostrzegały "useLayoutEffect does nothing on the server", gdy taki komponent był renderowany na serwerze; React 19 usunął to ostrzeżenie.
Jeśli pierwsza klatka wyrenderowana na serwerze bez pomiaru wyglądałaby źle, są dwa popularne rozwiązania: wyrenderuj neutralny widok zastępczy (na przykład ukryj tooltip, dopóki nie zostanie zmierzony) albo renderuj komponent tylko po stronie klienta:
function ClientOnly({ children }) {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
return mounted ? children : null;
}
useInsertionEffect
Jest też trzeci hook efektu, useInsertionEffect, który uruchamia się przed każdym efektem układu. Istnieje dla bibliotek CSS-in-JS, które generują tagi <style> w czasie działania: wstawienie stylów w tym momencie sprawia, że efekt układu, który mierzy element, widzi go już ostylowanego. Nie może aktualizować stanu, a refy nie są jeszcze podłączone, więc w kodzie aplikacji jest bezużyteczny. Jeśli nie piszesz biblioteki do stylowania, nie będziesz go potrzebować.
Najczęściej zadawane pytania
Czym różni się useEffect od useLayoutEffect?
Momentem uruchomienia. useLayoutEffect uruchamia się synchronicznie po tym, jak React zmienił DOM, ale przed odmalowaniem ekranu przez przeglądarkę, więc jego aktualizacje stanu są zastosowane, zanim użytkownik cokolwiek zobaczy. useEffect zwykle uruchamia się po odmalowaniu. API i tablica zależności są takie same.
Kiedy używać useLayoutEffect?
Gdy efekt odczytuje układ (rozmiar albo pozycję) i od razu na tej podstawie zmienia to, co jest renderowane, na przykład umieszcza tooltip nad przyciskiem albo pod nim. Bez niego użytkownik może przez jedną klatkę zobaczyć pierwszą, złą pozycję.
Czy useLayoutEffect szkodzi wydajności?
Blokuje przeglądarce odmalowanie ekranu, dopóki się nie skończy, razem z każdym ponownym renderowaniem, które wywoła. Wolny efekt układu sprawia, że cała strona wydaje się wolna, więc używaj useEffect, chyba że musisz uniknąć widocznego migotania.
Czy useLayoutEffect uruchamia się na serwerze?
Nie. Żaden efekt nie uruchamia się podczas renderowania na serwerze. Starsze wersje Reacta wypisywały ostrzeżenie, gdy komponent z useLayoutEffect był renderowany na serwerze; React 19 już tego nie robi, ale efekt nadal uruchamia się tylko w przeglądarce.