Strona główna / Artykuły

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.

Wydawca: REACTAPP.TOP17 min czytania4015 słów

Ź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 useLayoutEffect tylko wtedy, gdy coś musi się wydarzyć przed narysowaniem przez przeglądarkę; używaj useEffect dla 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.memo oferuje 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

  • Wyjaśnienie generatorów JavaScript: Funkcje, które można pauzować, i opóźniona iteracja — Dowiedz się, jak funkcje generatorów JavaScript mogą pauzować i wznowiać wykonywanie, implementować protokół iteratora oraz umożliwiać opóźnioną, energooszczędną transmisję danych.