Wyjaśnienie renderowania w React: aktualizacje stanu na piksele ekranu
Jak fazy renderowania, rekompensacji i zapisu w React łączą się z procesem renderowania w przeglądarce: układ, malowanie i kompozycja, od aktualizacji stanu do rzeczywistych pikseli na ekranie.
Źródła: Rendering Lifecycle in React: From State Update to Pixels on the Screen
Gdy pracuje się z React, często słyszy się wyrażenie „komponent został ponownie wyrenderowany”. Ale co tak naprawdę oznacza proces renderowania pod spodem?
Pojawia się tu kilka naturalnych pytań. Czy React wysyła aktualizacje do DOM-u w momencie zmiany stanu? Czy zmiana stanu zmusza każdy komponent do ponownego wyrenderowania? A kiedy React coś ponownie renderuje, czy oznacza to, że przeglądarka natychmiast ponownie rysuje całą stronę? Okazuje się, że żadne z tych twierdzeń nie jest prawdziwe.
Aby naprawdę zrozumieć cykl życia renderowania, trzeba pojąć dwa oddzielne systemy działające razem:
własny cykl życia renderowania React oraz pipeline renderowania przeglądarki.
Gdy zobaczysz, w jaki sposób te dwa systemy się łączą, koncepcje takie jak pojednanie, faza komitowania, useEffect, useLayoutEffect, ponowne renderowanie i memoizacja przestaną wydawać się czymś magicznym i zaczną mieć sens.
1. Ogólny obraz
User Interaction
│
▼
State / Props Change
│
▼
┌──────────────────────┐
│ React Render │
│ │
│ Component executes │
│ New UI is returned │
└──────────┬───────────┘
│
▼
Reconciliation
│
▼
┌──────────────────────┐
│ Commit Phase │
│ │
│ Update real DOM │
└──────────┬───────────┘
│
▼
┌──────────────────────────┐
│ Browser Rendering │
│ │
│ Style → Layout → Paint │
│ → Composite │
└────────────┬─────────────┘
│
▼
Screen
Główny wniosek jest następujący:
React określa, jak powinien wyglądać interfejs użytkownika oraz jakie zmiany w DOM są potrzebne. Następnie przeglądarka przechowuje te zmiany i zamienia je na rzeczywiste piksele na ekranie.
2. Co oznacza „rendering”?
Pojęcie rendering jest używane w kilku znaczeniach.
W kontekście React rendering zazwyczaj oznacza to:
React wywołuje funkcję komponentu i określa, jaki powinien być ostateczny interfejs użytkownika.
Rozważmy następujący komponent:
function Counter() {
const [count, setCount] = useState(0);
return (
<button>
Count: {count}
</button>
);
}
Gdy React go renderuje, wykonywany jest treść funkcji:
Counter()
↓
returns React element
↓
<button>Count: 0</button>
To nie oznacza, że przeglądarka już narysowała przycisk na ekranie.
Zanim pojawi się cokolwiek wizualnego, React musi jeszcze zdecydować, czy rzeczywiście konieczna jest zmiana w DOM-ie.
Krótko mówiąc:
React Render
≠
DOM Update
≠
Browser Paint
Renderowanie i aktualizacja DOM-u to dwie odrębne operacje.
3. Cykl życia renderowania w przeglądarce
Zanim przejdziemy do mechanizmów wewnętrznych Reacta, przydatne jest zrozumienie tego, co robi sama przeglądarka.
Załóżmy, że mamy taką strukturę:
<h1 class="title">
Hello World
</h1>
W połączeniu z tą skórą stylową:
.title {
color: red;
font-size: 30px;
}
Zanim ten treść w ogóle pojawi się wizualnie, przeglądarka przechodzi przez serię kroków wewnętrznych.
Społeczna wersja tego procesu wygląda tak:
HTML
↓
DOM
↓
CSS
↓
CSSOM
↓
Render Tree
↓
Layout
↓
Paint
↓
Composite
↓
Screen
Przejdźmy przez każdy etap.
4. HTML → DOM
Przeglądarka czyta HTML i buduje DOM, skrót od Document Object Model.
Biorąc pod uwagę ten kod:
<body>
<h1>Hello World</h1>
<p>Welcome</p>
</body>
Przeglądarka tworzy strukturę drzewiastą mniej więcej w ten sposób:
Document
│
└── body
├── h1
│ └── "Hello World"
│
└── p
└── "Welcome"
Następnie JavaScript może bezpośrednio analizować i modyfikować tę strukturę:
document.querySelector("h1");
W istocie DOM odpowiada na jedno pytanie:
Które elementy istnieją na stronie?
5. CSS → CSSOM
Oprócz DOM-u przeglądarka analizuje również pliki CSS i buduje CSSOM, czyli CSS Object Model.
Na przykład:
h1 {
color: red;
font-size: 30px;
}
Na podstawie tego przeglądarka określa, które style odnoszą się do których elementów.
Koncepcyjnie wygląda to w ten sposób:
h1
├── color: red
└── font-size: 30px
CSSOM odpowiada na inne pytanie:
Jak powinien wyglądać wizualny styl każdego elementu?
6. DOM + CSSOM → Drzewo renderowania
Następnie przeglądarka łączy DOM i CSSOM, aby stworzyć Drzewo Renderowania.
Warto zwrócić uwagę na jedną subtelność:
Nie każdy węzeł w DOM znajduje odbicie w Drzewie Renderowania.
Na przykład weźmy ten kod:
<div class="hidden">
Hello
</div>
W połączeniu z tą zasadą:
.hidden {
display: none;
}
Element div istnieje w DOM:
DOM
└── div
Ale ponieważ zawiera on:
display: none;
jest całkowicie wykluczony z drzewa renderowania wizualnego.
7. Układ
Gdy przeglądarka wie dokładnie, które elementy muszą być wyświetlone, oblicza ich geometrię.
Obejmuje to kalkulację takich wartości jak:
- Szerokość
- Wysokość
- Pozycja
- Marginesy
- Odstępy wewnętrzne
- Koordynaty
- Sposób, w jaki elementy są względem siebie rozmieszczone
Weźmy tę zasadę jako przykład:
.box {
width: 200px;
height: 100px;
margin-left: 50px;
}
Przeglądarka musi wygenerować konkretne wartości w taki sposób:
x = 50px
y = ...
width = 200px
height = 100px
Ten krok obliczeń nazywany jest Layout, a często również Reflow.
8. Paint
Gdy layout jest już ukończony, przeglądarka posiada współrzędne dla każdego widocznego elementu.
Kolejnym krokiem jest narysowanie tego, jak te elementy faktycznie wyglądają.
Rozważmy następujące stylizacje:
.box {
background: blue;
color: white;
border: 2px solid black;
}
Proces Paint obejmuje takie elementy jak:
- Tło
- Tekst
- Bordiury
- Cienie
- Zdjęcia
- Inne elementy wizualne
W kolejności:
Layout
↓
Paint
↓
Pixels
9. Composite
Dzisiejsze przeglądarki mogą podzielić stronę na kilka odrębnych warstw.
Naprzимер:
Layer 1 → Page background
Layer 2 → Header
Layer 3 → Content
Layer 4 → Modal
Następnie komponentor przeglądarki łączy wszystkie te warstwy w jedno zdjęcie, które pojawia się na ekranie.
Pewne właściwości CSS, takie jak:
transform
opacity
Zazwyczaj można to animować wyłącznie poprzez kompozycję, unikając bardziej pracochłonnego procesu układania, który wymagają właściwości wpływające na geometrię.
To jeden z powodów, dla których animowanie właściwości transform jest zwykle preferowanym podejściem.
10. Cykl życia renderowania w React
Gdy już omówiliśmy stronę przeglądarki, przyjrzyjmy się temu, jak React zarządza renderowaniem.
Korzystny model mentalny dzieli ten proces na cztery etapy:
1. Trigger
2. Render
3. Reconciliation
4. Commit
5. Browser Rendering
Przejdźmy przez każdy z nich po kolei.
11. Faza 1 — Wyzwalacz
Zanim cokolwiek innego się wydarzy, React potrzebuje sygnału, który mówi:
"Interfejs użytkownika może wymagać aktualizacji."
Ten sygnał nazywany jest wyzwalaczem renderowania.
Typowe wyzwalacze to:
1. Aktualizacja stanu: setCount(10);
2. Zmiana właściwości: <Child count={count} />
Gdy komponent nadrzędny jest renderowany ponownie z nową wartością dla count, jego komponenty potomne mogą w rezultacie otrzymać nowe właściwości.
3. Aktualizacja kontekstu: useContext(MyContext);
Za każdym razem, gdy zmienia się wartość odczytywana z kontekstu, komponenty, które ją wykorzystują, mogą wymagać aktualizacji.
4. Aktualizacja zewnętrznego magazynu: Biblioteki i API również mogą wywołać aktualizacje w React.
Przykłady obejmują:
Redux
Zustand
useSyncExternalStore
Pierwsze uruchomienie aplikacji również stanowi sygnał do aktualizacji.
12. Faza 2 — Renderowanie
Gdy React otrzyma sygnał o aktualizacji, przechodzi do fazy renderowania, w której uruchamia komponenty, aby określić nową interfejs użytkownika.
Weźmy ten przykład:
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}
Na początku count wynosi 0. React wywołuje Counter() i generuje:
<button>
Count: 0
</button>
Gdy zostanie kliknięty przycisk i uruchomi się ten kod:
setCount(c => c + 1);
count staje się 1, a React ponownie uruchamia komponent, co skutkuje:
<button>
Count: 1
</button>
13. Ważne: Renderowanie NIE oznacza aktualizacji DOM
To kluczowa różnica w modelu Reacta.
Gdy React wykonywa ten funkcję:
function Counter() {
console.log("Counter rendered");
return <button>Count: 1</button>;
}
po prostu oblicza, jak powinien wyglądać interfejs użytkownika. Nie oznacza to automatycznie, że DOM uległ zmianie lub że przeglądarka odmalowała coś nowego.
Rzeczywista sekwencja wygląda tak:
Component executes
↓
React creates new element representation
↓
Reconciliation
↓
Determine required DOM changes
↓
Commit changes
To prowadzi do ważnej zasady:
Renderowanie komponentu nie oznacza automatycznie aktualizacji DOM.
14. Faza 3 — Porównanie
Na tym etapie React porównuje stary i nowy interfejs użytkownika, aby ustalić dokładnie, co się zmieniło.
Załóżmy, że poprzednia struktura wyglądała tak:
button
└── "Count: 0"
A nowa struktura wygląda tak:
button
└── "Count: 1"
React potrafi to ustalić:
button → same
text → changed
Dlatego musi edytować tylko tę część DOM-u, która rzeczywiście uległa zmianie:
button.textContent = "Count: 1";
Głównym celem jest unikanie rozbierania i odbudowy całego drzewa DOM, gdy zmieniła się tylko niewielka jego część.
15. Klucze i synchronizacja
Klucze są najważniejsze, gdy React pracuje z listami.
Na przykład:
items.map(item => (
<li key={item.id}>
{item.name}
</li>
))
React polega na kluczach, aby śledzić, który element jest którym podczas kolejnych renderów. Załóżmy, że zaczynamy od:
Before:
A
B
C
i kończymy na:
After:
A
C
Przy stabilnych kluczach React może poprawnie wywnioskować:
A → same
B → removed
C → same
To dostarcza Reactowi znacznie bardziej wiarygodnych informacji do pracy podczas synchronizacji.
Unikaj używania indeksów tablicy jako kluczy, gdy elementy listy mogą zostać przestawione, dodane lub usunięte.
16. Faza 4 — Zatwierdzenie
Gdy React ustali, co musi zostać zmienione, przechodzi do fazy komitowania, w której faktycznie aplikuje te zmiany w rzeczywistym DOM-ie.
Wyobraź sobie element przycisku, którego etykieta zmienia się z pokazywania liczby zero na liczbę jeden po zakończeniu tej fazy.
W tym momencie wewnętrzne drzewo Reacta i żywy DOM w końcu się pokrywają.
Faza komitowania to odrębny krok od fazy renderowania:
Render Phase
↓
Calculate what should change
Commit Phase
↓
Actually apply DOM changes
17. Faza renderowania vs faza komitowania
Zrozumienie tego podziału to jedna z najczęściej sprawdzanych kwestii przez rekruterów.
Faza renderowania
W trakcie tej fazy React:
- Uruchamia funkcje komponentów
- Określa następną wersję interfejsu
- Przeprowadza synchronizację
- Stwierdza, co musi zostać zmienione
Component
↓
React elements
↓
Reconciliation
Faza komitowania
W trakcie tej fazy React:
- Stosuje rzeczywiste mutacje DOM
- Uaktualnia wszystkie referencje
- Rozpoczyna wykonywanie efektów układu związanych z tą aktualizacją
React result
↓
Real DOM
Oto uproszczony obraz tego procesu:
React
│
▼
┌──────────────┐
│ Render Phase │
└──────┬───────┘
│
▼
┌───────────────┐
│ Commit Phase │
└──────┬────────┘
│
▼
Real DOM
18. Co dzieje się po zatwierdzeniu?
Gdy fazę zatwierdzania ukończono, przeglądarka przejmuje kontrolę i zauważa, że DOM uległ zmianie.
w zależności od tego, co się zmieniło, przeglądarka może musieć przejść przez kilka etapów:
DOM Changes
↓
Style Calculation
↓
Layout
↓
Paint
↓
Composite
Nie każda zmiana wymaga przeprowadzenia wszystkich tych etapów, aby osiągnąć istotny efekt — na przykład zmiana może wymagać jedynie ponownego narysowania, bez konieczności pełnego przeliczania układu.
To właśnie w tym momencie własny cykl życia React przekazuje kontrolę do wcześniej opisanego pipeline renderowania przeglądarki.
19. useLayoutEffect
useLayoutEffect jest wywoływany po tym, jak React zatwierdzi aktualizacje DOM-u, ale przed tym, jak przeglądarka zdąży narysować nowy wygląd.
Sekwencja wydarzeń wygląda następująco:
React Render
↓
Commit
↓
DOM updated
↓
useLayoutEffect
↓
Browser Paint
function Tooltip() {
const ref = useRef(null);
useLayoutEffect(() => {
const rect = ref.current.getBoundingClientRect();
console.log(rect.width);
}, []);
return (
<div ref={ref}>
Tooltip
</div>
);
}
Ten hook jest odpowiednim narzędziem, gdy potrzebujesz:
- Pomierzyć elementy w DOM-u
- Odczytać aktualne wartości układu
- Zastosować dostosowania wizualne synchronicznie, zanim użytkownik cokolwiek zobaczy
Jedna uwaga: jeśli praca wewnątrz useLayoutEffect jest intensywna, może to opóźnić rysowanie i sprawić, że interfejs będzie wydawał się wolny.
20. useEffect
useEffect, w przeciwieństwie do niego, służy do efektów, które nie muszą blokować procesu rysowania w przeglądarce.
useEffect(() => {
fetch("/api/products");
}, []);
Render
↓
Commit
↓
Browser Paint
↓
useEffect
Właściwe szczegóły planowania są nieco bardziej złożone, ale kluczowa zasada praktyczna brzmi:
Korzystaj z
useLayoutEffecttylko wtedy, gdy coś musi się wydarzyć przed narysowaniem przez przeglądarkę; używajuseEffectdla wszystkiego, co może zostać wykonane po tym, jak interfejs jest już widoczny.
21. Pełny przykład
import { useEffect, useLayoutEffect, useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
console.log("Render");
useLayoutEffect(() => {
console.log("Layout Effect");
});
useEffect(() => {
console.log("Effect");
});
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}
Koncepcyjnie sekwencja zdarzeń wygląda tak:
User clicks button
↓
setCount()
↓
State update scheduled
↓
Counter() executes
↓
"Render"
↓
React reconciles
↓
React commits DOM update
↓
"Layout Effect"
↓
Browser paints updated UI
↓
"Effect"
22. Czy ponowne renderowanie rodzica powoduje renderowanie dziecka?
Weźmy ten przykład:
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(c => c + 1)}>
{count}
</button>
<Child />
</>
);
}
Gdy aktualizuje się count:
Parent renders
↓
Child is encountered
↓
Child may render as part of reconciliation
So,
Parent state update
↓
Parent render
↓
Child can render
To zachowanie jest jednym z powodów, dla których istnieje React.memo jako narzędzie optymalizacyjne.
23. React.memo
Załóżmy, że masz:
const Child = React.memo(function Child() {
console.log("Child rendered");
return <p>Hello</p>;
});
Jeśli rodzic zostanie ponownie zrenderowany, ale props przekazane do dziecka pozostają identyczne pod względem referencji, React może całkowicie pominąć ponowne renderowanie tego zmemorizowanego dziecka.
Oto wizualne przedstawienie tej koncepcji:
Parent update
↓
Parent renders
↓
React.memo checks Child props
↓
Props unchanged
↓
Skip Child render
Należy pamiętać jednak o tym:
React.memooferuje skrót w zakresie wydajności — nie gwarantuje jednak, że komponent nigdy więcej się nie renderuje.
Zmiany kontekstu, stan wewnętrzny oraz inne czynniki mogą nadal zmusić go do aktualizacji, niezależnie od mechanizmu memoizacji.
24. useMemo i useCallback
Te dwa hooki są często błędnie rozumiane.
useMemo: Memoizuje obliczoną wartość.
const result = useMemo(() => expensiveCalculation(data), [data]);
useCallback: Memoizuje odwołanie do funkcji.
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
Oba są rzeczywiście przydatne w sytuacjach, gdy równość referencyjna ma znaczenie dla unikania powtarzających się operacji — szczególnie przy przekazywaniu właściwości do zmemorizowanych komponentów potomnych.
Mimo to stosowanie ich wszędzie jako standardu zwykle dodaje złożoność do kodu bez żadnej rzeczywistej korzyści pod względem wydajności.
25. Renderowanie w React nie jest tym samym co rysowanie przez przeglądarkę
Tę kwestię warto wyraźnie podkreślić.
Załóżmy następujący komponent:
function App() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>
{count}
</button>
<p>Hello</p>
</div>
);
}
Gdy count ulega zmianie:
App renders
↓
New React tree created
↓
Reconciliation
↓
Only count text differs
↓
Commit small DOM update
↓
Browser rendering
React niekoniecznie odbuduje ten kod od zera:
<div>
<button>...</button>
<p>Hello</p>
</div>
26. Virtual DOM — prawidłowe zrozumienie koncepcji
Często spotyka się twierdzenie, że React polega na Virtual DOM, aby przyspieszyć pracę przeglądarki. To uproszczenie.
W rzeczywistości React przechowuje w pamięci wewnętrzny opis interfejsu użytkownika i wykorzystuje mechanizm porównywania, aby ustalić, co się zmieniło.
Sama przeglądarka nie ma pojęcia o Virtual DOM. Pracuje jedynie z takimi elementami jak HTML, DOM, CSS oraz piksele.
Koncepcyjnie proces wygląda w ten sposób:
React Component
↓
React Element Tree
↓
Reconciliation
↓
DOM Mutations
↓
Browser
↓
Pixels
Wirtualny DOM to coś, czego przeglądarka nigdy faktycznie nie renderuje.
27. Ponowne renderowanie vs Przerysowywanie
Są to dwa odrębne pojęcia, których nie należy mylić.
Ponowne renderowanie w React
Dzieje się tak, gdy funkcja komponentu jest wykonywana ponownie — w zasadzie uruchamia się Component().
Przerysowywanie przez przeglądarkę
Dzieje się tak, gdy przeglądarka ponownie rysuje piksele na ekranie z powodu zmiany jakiejś właściwości wizualnej — to jest etap Paint.
Pamiętaj więc: ponowne renderowanie w React nie jest równoznaczne z przerysowywaniem przez przeglądarkę.
Możliwe jest, że komponent zostanie ponownie wyrenderowany, a mimo to struktura DOM pozostanie identyczna. Z drugiej strony przeglądarka może przerysowywać piksele z powodów, które nie mają nic wspólnego z renderowaniem żadnego komponentu React.
28. Przepływ danych vs Przerysowywanie
Weźmy ten przykład: element.style.width = „500px“;
Zmiana szerokości elementu zazwyczaj zmusza do ponownego obliczenia układu:
Style
↓
Layout
↓
Paint
↓
Composite
A teraz porównajmy to z: element.style.backgroundColor = „red“;
Tutaj przeglądarz zazwyczaj nie musi ponownie wykonywać obliczeń geometrycznych:
Style
↓
Paint
↓
Composite
A w przypadku pewnych zmian transformacji lub przenikliwości przeglądarz może potrzebować jedynie:
Composite
Zrozumienie tej różnicy jest bardzo ważne podczas optymalizacji animacji lub interfejsów wymagających intensywnego renderowania.
29. Zastosowanie tego w rzeczywistym scenariuszu React
Wyobraźmy sobie typową stronę produktu e-commerce zbudowaną w ten sposób:
Product Page
├── Navbar
├── Product Gallery
├── Product Information
├── Add To Cart
├── Reviews
└── Recommendations
Załóżmy, że użytkownik kliknie przycisk Dodaj do koszyka, co aktualizuje liczbę produktów w koszyku.
Koncepcyjnie sekwencja zdarzeń może wyglądać tak:
User clicks "Add to Cart"
↓
Cart state updates
↓
React schedules update
↓
Relevant components render
↓
React reconciles new tree
↓
Only required DOM changes are committed
↓
Browser performs necessary rendering work
↓
Updated cart count appears
W dobrze zorganizowanej aplikacji aktualizacja liczby produktów w koszyku nie powinna zmuszać do niepotrzebnego odbudowywania całej interfejsu użytkownika. Właśnie dlatego decyzje dotyczące granic komponentów, miejsca przechowywania stanu, mechanizmów memoizacji oraz ogólnej architektury renderowania mają duże znaczenie.
30. Kiedy kosztowny proces renderowania staje się problemem wydajności
function ProductList({ products }) {
return products.map(product => (
<ExpensiveProductCard
key={product.id}
product={product}
/>
));
}
Załóżmy, że ta lista renderuje 10 000 produktów, a jakaś niepowiązana zmiana stanu powoduje marnotrawstwo zasobów w wielu komponentach — wydajność może znacznie się pogorszyć.
Powszechne techniki łagodzenia tego problemu to:
React.memo
+
Stable props
+
useMemo/useCallback where justified
+
Virtualization
+
Pagination
+
Code splitting
W przypadku szczególnie dużych list wirtualizacja okazuje się szczególnie skuteczna:
10000 items
↓
Only visible ~20-50 items rendered
31. Jak proces renderowania współdziała z równoczesnym działaniem Reacta
Nowsze wersje Reacta oferują bardziej zaawansowane możliwości planowania. React może przypisywać priorytet różnym aktualizacjom i, gdy to ma sens, wstrzymywać lub odkładać prace związane z renderowaniem.
Kluczowy model myślowy, którego należy przestrzegać, to:
Render work
↓
Can be scheduled
↓
Can be interrupted in modern React
↓
Eventually committed
Dlatego właśnie efekty uboczne nie powinny znajdować się w logice renderowania komponentu:
function Component() {
fetch("/api/data"); // ❌ Side effect during render
return <div>Hello</div>;
}
Zamiast tego należy użyć hooka effect lub odpowiedniego wzorca pobierania danych. W idealnym przypadku faza renderowania powinna pozostać czysta.
32. Zalety czystego renderowania
Koncepcyjnie komponent Reacta powinien zachowywać się jak funkcja czysta:
UI = f(props, state)
Przy tych samych propach i stanie powinien zawsze przedstawiać ten sam interfejs użytkownika. Na przykład:
function Greeting({ name }) {
return <h1>Hello {name}</h1>;
}
To zachowanie jest przewidywalne i bezpieczne.
W przeciwieństwie do tego, robienie czegoś takiego wewnątrz funkcji renderowania jest problematyczne:
function Component() {
localStorage.setItem("x", "1"); // ❌
fetch("/api"); // ❌
return <div>Hello</div>;
}
To wprowadza efekty uboczne w fazę, którą React może wykonać więcej niż raz lub zaplanować w nieoczekiwany sposób.
Efekty uboczne powinny znajdować się gdzie indziej — w funkcjach effects, obsługiwanie zdarzeń lub mechanizmach pobierania danych dostarczanych przez framework.
33. Cykl życia renderowania — kompletny model mentalny
USER ACTION
│
▼
State / Props Update
│
▼
React Schedules Work
│
▼
┌──────────────┐
│ RENDER │
│ │
│ Components │
│ execute │
└──────┬───────┘
│
▼
Reconciliation
│
▼
┌──────────────┐
│ COMMIT │
│ │
│ DOM mutation │
└──────┬───────┘
│
▼
useLayoutEffect
│
▼
Browser Rendering
│
▼
Style Calculation
│
▼
Layout
│
▼
Paint
│
▼
Composite
│
▼
SCREEN
│
▼
useEffect
Częste pytania na rozmowach kwalifikacyjnych
Q1. Czy aktualizacja stanu natychmiast aktualizuje DOM?
Nie. Aktualizacja stanu jedynie planuje zadania do wykonania przez React. React renderuje komponent, porównuje wynik z tym, co było wcześniej, i dopiero wtedy wprowadza te zmiany w DOM, które są rzeczywiście konieczne.
setState
↓
Render
↓
Reconciliation
↓
Commit
↓
DOM
Q2. Czy każde renderowanie prowadzi do aktualizacji DOM?
Nie. React może renderować komponent i mimo to stwierdzić, że uzyskany opis interfejsu jest identyczny z tym, co już znajduje się na ekranie, w takim przypadku pomija modyfikację DOM.
Render
↓
Compare
↓
No meaningful DOM difference
↓
No DOM mutation
Q3. Czy ponowne renderowanie w React zmusza przeglądarkę do ponownego rysowania?
Nie. Renderowanie w React i rysowanie w przeglądarce to dwa niezależne procesy.
React Render ≠ Browser Paint
Q4. Co oznacza rekonsolidacja?
Rekonsolidacja to sposób, w jaki React porównuje świeżo obliczony drzewo interfejsu z poprzednim, aby ustalić dokładnie, co musi ulec zmianie.
Q5. Co dzieje się podczas fazy commit?
Podczas commit React aplikuje wymagane zmiany w środowisku hosta — na przykład aktualizuje rzeczywiste węzły DOM w przeglądarce.
Q6. W czym różnią się useEffect i useLayoutEffect?
Po prostu pamiętaj:
useLayoutEffect
→ after DOM commit, before paint
useEffect
→ after commit and generally after paint
Użyj useLayoutEffect, gdy musisz synchronicznie zmierzyć lub dostosować układ przed tym, jak użytkownik zobaczy nowy ramek. Do większości efektów ubocznych, które nie wymagają blokowania procesu rysowania, stosuj useEffect.
Q7. Co powoduje renderowanie komponentu?
Zwykłe przyczyny to: początkowe załadowanie komponentu, aktualizacja stanu, ponowne renderowanie komponentu rodzica, zmiana właściwości, aktualizacja kontekstu lub aktualizacja z zewnętrznego magazynu.
Q8. Jak można zoptymalizować proces renderowania? [IMP]
Zamiast domyślnie stosować useMemo wszędzie, najpierw określ, gdzie znajduje się rzeczywisty wąskie gardło. Następnie rozważ następujące podejścia:
1. Trzymaj stan blisko miejsca, gdzie jest używany
App
↓
Everything depends on state
prefer:
Feature
↓
Local state
Należy preferować węższe umiejscowienie, gdy globalny stan faktycznie nie jest konieczny.
2. Memetyzuj kosztowne komponenty selektywnie
const ProductCard = React.memo(ProductCardComponent);
3. Uważaj na tworzenie obiektów, które zakłócają memetyzację
Zamiast wielokrotnie tworzyć nowy obiekt bezpośrednio w kodzie w ten sposób:
<Child config={{ theme: "dark" }} />
Możesz potrzebować stabilnego odniesienia, jeśli Child jest otoczony przez React.memo i tożsamość ma znaczenie — ale nie naprawiaj tego, dopóki rzeczywiście nie stanie się to problemem.
4. Wirtualizuj długie listy
Zamiast renderować wszystkie 10 000 węzłów DOM naraz, renderuj tylko te, które są obecnie widoczne.
5. Odkładaj ładowanie kosztownych funkcji
const AdminDashboard = lazy(() => import("./AdminDashboard"));
To zmniejsza ilość JavaScriptu, który musi zostać pobraany i wykonywany od razu.
6. Preferuj techniki renderowania przyjazne przeglądarce
Gdzie to możliwe, używaj do animacji tanich właściwości takich jak transformacja i przenikliwość, unikając konieczności wykonywania kosztownych ponownych obliczeń układu na każdym kadrze.
Literatura pokrewna
- React 19.2 wyjaśnione: Activity, useEffectEvent i renderowanie statyczne — Dowiedz się, jak nowy komponent Activity, hook useEffectEvent oraz częściowe renderowanie statyczne w React 19.2 eliminują ukryte koszty wydajności w nowoczesnych interfejsach użytkownika.
- Przestań synchronizować stan z useEffect: Bezpieczniejszy wzorzec Reacta — Dowiedz się, dlaczego używanie useEffect do synchronizacji stanu pochodnego powoduje sytuacje konkurencyjne i dodatkowe renderowania, oraz jak zastąpić to metodą wyprowadzania stanu w czasie renderowania i użyciem atrybutu key.