Галоўная / Артыкулы

Паспрацаванне з React: адкладкі стану ў пікселі экрана

Як фазы render, reconciliation і commit у React з’єднуються з процесам відрисовування браузером: layout, paint і composite, від оновлення стану до фактичних пікселів екрана.

Выдавец: REACTAPP.TOP17 хв чытання4015 слоў

Крыніцы: Rendering Lifecycle in React: From State Update to Pixels on the Screen

Працюючы з React, часта суём фразу "компонент перерэндараваецца". Але што на самай працэ ўсё-такі значыць перерэндараванне?

У гэтым контексте выклікаюцца калькі практычных запытанняў. Чы Реакт негайна дадае апдэйты DOM, калі змянюецца стан? Чы змена стану прыводзіць да таго, што кожны компонент павінен перерэндаравацца? І калі Реакт зноў перерэндараваець які-небудзь элемент, чы гэта значыць, што браузер негайна перамальоввае весь сторнак? Як выявілася, нічога з гэтага не ўсуне.

Ёнколі хочаце правильна зрозумець жыцёвы цикл перерэндаравання, трэба разумець двы аднальныя системы, якія працуюць разам:

Свой жыцёвы цикл перерэндаравання Реакта і практыка перамальоввання браузера.

Калі вы паблічнае, як заўязаныя этыя два системы, такія поняття, як прымирэнне, фаза адправкі, useEffect, useLayoutEffect, павтарэнне адрасаўвання і мемаізацыя, перестаюць здавацца чамусь магічным і становяцца зрозумелымі.

1. Загальная картына

У загальных рысах прабег справы выглядае так:

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

Галоўны вывад здэсь такі:

React вычышае, як павінна выглядаць інтэрфейс і якія змены ў DOM неабходны. Потым браузер берае этыя змены і ператварае іх у рэальныя пікселі на экране.

2. Што значыць «адрасаўванне»?

Тэрмін «адрасаўванне» вжываецца ў болей чым аднам значэнні.

У контексте React адрасаўванне зазвычай значыць гэта:

React вызывае функцыю вашага компонента і вычышае, як павінен выглядаць рэзультатны інтэрфейс.

Разглядзім этот компонент:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button>
      Count: {count}
    </button>
  );
}

Калі React яго атрыбутуе, выконваецца тэла функцыі:

Counter()
   ↓
returns React element
   ↓
<button>Count: 0</button>

Гэта не значыць, што браузер уже намаліў кнопку на экране.

Перш чым адбудзецца ўсё візуальна, React все ж должен выявіць, чы рэальна DOM патрэбуе змены.

Корача кажучы:

React Render
     ≠
DOM Update
     ≠
Browser Paint

Атрыбутаванне та апдэйт DOM — гэта два разныя процесы.

3. Жыцёвы цикл атрыбутавання браузерам

Перш чым заглыбляцца ў внутрошняе строенне React, корыстна зразумець, што сам браузер робіць.

Уявіце, што у вас ёсць такі маркап:

<h1 class="title">
  Hello World
</h1>

У поўнасці ўзаедна з гэтым стайл-шытом:

.title {
  color: red;
  font-size: 30px;
}

Перш чым гэты контент хоць як будзе візуальна прыказаны, браузер пераходзіць праз ряд актыўных внутрошняях крокаў.

Спрасцаваная версія гэтага процесу выглядае так:

HTML
 ↓
DOM
 ↓
CSS
 ↓
CSSOM
 ↓
Render Tree
 ↓
Layout
 ↓
Paint
 ↓
Composite
 ↓
Screen

Разберамся з кожным этапам.

4. HTML → DOM

Браузер чытае HTML і стварае DOM, якое ў складчыні значыць Model об’екта дакумента.

З урахоўваннем гэтага маркапу:

<body>
  <h1>Hello World</h1>
  <p>Welcome</p>
</body>

Браузер стварае дрэваўую структуру прыблізна такой:

Document
   │
   └── body
       ├── h1
       │   └── "Hello World"
       │
       └── p
           └── "Welcome"

Пасля чаго JavaScript можа безпасэродзе запытваць і маніпуляваць гэтай структурам:

document.querySelector("h1");

По суты, DOM адпавядае на аднае пытанне:

Калі элементы існуюць на сторанцы?

5. CSS → CSSOM

Паралельна з DOM браузер таксама парасканаввае ваш CSS і стварае CSSOM, тое ў складчыні Model об’екта CSS.

Напрыклад:

h1 {
  color: red;
  font-size: 30px;
}

На адной з гэтых падстав браузер выважае, якія стылі паслядуюць для кожнага элемента.

Канцэптуальна, гэта выглядае так:

h1
 ├── color: red
 └── font-size: 30px

CSSOM адпавядае на іншае пытанне:

Як следуе візуальна настроіць кожны элемент?

6. DOM + CSSOM → Дрэва рэндараўання

Далей браузер з’еднае DOM і CSSOM, ў результате чаго ствараеяцца Дрэўо адраджэння.

Є адна тонкасць, якаю варта згадаць:

Не кожны вузол у DOM будзе представлены ў Дрэўе адраджэння.

Напрыклад, возьмім такі маркап:

<div class="hidden">
  Hello
</div>

У поўнасці разам з гэтым правілам:

.hidden {
  display: none;
}

Элемент div ўсё ж існуе ў DOM:

DOM
 └── div

Але осколькі ён мае:

display: none;

ён цэлкам адключаецца з візуальнага Дрэўа адраджэння.

7. Распакоўка

Калі браузер точна ведае, якія элементы трэба паказаць, ён вырахоўвае іх геаметрыю.

Гэта включае вычысленне такіх показнікаў, як:

  • Шырына
  • Высота
  • Пазіцыя
  • Маржы
  • Падпертыя
  • Координаты
  • Як элементы адносуюцца прасторова адна да другой

Возьмім гэтае правіла як прыклад:

.box {
  width: 200px;
  height: 100px;
  margin-left: 50px;
}

Браузер павінен выдаваць конкрэтныя значэння па такім прыемліванні:

x      = 50px
y      = ...
width  = 200px
height = 100px

Этап вычыслення называецца Layout, і яго таксама часта адначасова называюць Reflow.

8. Paint

Пасля завершэння форматавання браузер мае координаты для кожнага відразлівага элемента.

Наступны крок — адразліванне таго, як на самай працэ выглядаюць гэтыя элементы.

Разглядзім такое стайліванне:

.box {
  background: blue;
  color: white;
  border: 2px solid black;
}

Процэс Paint абарачвае такія элементы:

  • Фон
  • Тэкст
  • Краі
  • Цені
  • Адразліванні
  • Іншыя візуальныя элементы

У парадку:

Layout
  ↓
Paint
  ↓
Pixels

9. Composite

Сучасныя браузеры можаюць раздзеліць сторанку на кальколька аднальных шароў.

Напрыклад:

Layer 1 → Page background
Layer 2 → Header
Layer 3 → Content
Layer 4 → Modal

Пасля гэтага композітор браузера з’еднае всі гэтыя шары ў адны адразліванне, які пасля выйдае на экран.

Адзінкія атрыбуты CSS, такія як:

transform
opacity

Зазвычай гэта можа быць анімавана толькі за дапамою композіцыі, чым ухілваецца ад болей сложной роботы з макетам, якая трэбаецца для атрыбутаў, якія вплываюць на геаметрыю.

Гэта ў частыні прычыны, чаму анімаванне transform зазвычай ўважаецца аптимальным падходам.

10. Жыцёвы цикл рэндарування React

Калі мы вже розглянулі сторону браузера, давайце перейдзем да таго, як React керуе рэндаруванням.

Прыдатны ментальны модэль дзеліць гэты процес на чатыры стадіі:

1. Trigger
2. Render
3. Reconciliation
4. Commit
5. Browser Rendering

Давайце розглянем кожную з іх.

11. Фаза 1 — Актывацыя

Перш чым ўсё інше пачнёцца, React патрэбны сігнал, які гаворыць:

"Інтерфейс можа патрабаваць апдэйтаў."

Гэты сігнал называецца актывацыяй рэндарування.

Тыповыя прычыны актывацыі включаюць:

1. Апдэйт стану: setCount(10);

2. Змена пропсаў: <Child count={count} />

Калі родны компанент перыядруецца з новай значэннем для count, яго дзеціны компаненты можаць атрымаць новыя пропсы як наследак.

3. Апдэйт контекста: useContext(MyContext);

Калі-небудзь зменяецца значэнне, чытана з контекста, компаненты, якія яго выкарыстоўваюць, можаць патрабаваць апдэйта.

4. Апдэйт зовнішняе базы дадзеных: Бібліятэкі і API таксама можуць спрыяць апдэйту React.

Прыклады включаюць:

Redux
Zustand
useSyncExternalStore

Першы запуск аплікацыі таксама лягае ў кантэкст спрычыны апдэйта.

12. Фаза 2 — Render

Калі React атрымлеў інформацыю пра апдэйт, ён пераходзіць у фазу render, дзе запускае компаненты, каб вырашыць, які будзе новы інтерфейс.

Возьмім такі прыклад:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}

Спачатку count доравнае 0. React вызывае Counter() і стварае:

<button>
  Count: 0
</button>

Калі нажата кнопка і запускаецца гэты код:

setCount(c => c + 1);

count становіцца 1, і React парадзеўна выкананая компонента, чыго рэзультат:

<button>
  Count: 1
</button>

13. Важнае: Render НЕ значыць апдэйт DOM

Эта ўлучнае размежаванне ў модэлі React.

Калі React выкананая гэтую функцыю:

function Counter() {
  console.log("Counter rendered");

  return <button>Count: 1</button>;
}

яна проста вырахоўвае, як дэвэльперскі інтерфейс дзеўэльнае выглядаць. Гэта не прымусова значыць, што DOM змяніўся або што браузер перамалюваў які-небудзь элемент.

Рэальная последовасць выглядае так:

Component executes
       ↓
React creates new element representation
       ↓
Reconciliation
       ↓
Determine required DOM changes
       ↓
Commit changes

Што прыводзіць да ключовага правіла:

Выкананне компоненты не прымусова ператвараецца ў апдэйт DOM.

14. Фаза 3 — Спакоі

На гэтым этапе React парабягае стары і новы дэвэльперскі інтерфейс, каб з’ясаваць, што самэўна зменілася.

Сказаўм, што пакананая структура выглядала так:

button
 └── "Count: 0"

А новая структура выглядае так:

button
 └── "Count: 1"

React можа вывучыць:

button → same
text   → changed

Таму яму трэба зменіць толькі тую частку DOM, яка насправды зменілася:

button.textContent = "Count: 1";

Галоўная мета — ухіліцца ад разбірання і практычнага стварэння всёго дрэва DOM, калі зменілася толькі невелика частка.

15. Ключы і супрацоўка з данымі

Ключы маюць вялікое значэнне, калі React працуе зі спісамі.

Напрыклад:

items.map(item => (
  <li key={item.id}>
    {item.name}
  </li>
))

React выкарыстоўвае ключы, каб стежыць за тым, які элемент ёсць каму праз рэндары. Падазроўваю, што вы пачынаеце з:

Before:

A
B
C

і заканчваеце з:

After:

A
C

За наявнасці стабільных ключоў React можа правильна вывучыць:

A → same
B → removed
C → same

Это дае React набліжна болей надзеяныя даны для працы пад час супрацоўкі з данымі. Ухіліваюцеся ад выкарыстоўвання індэксаў масівы як ключоў, калі элементы спісу могу быць пераранжаваны, даданы або адключаны.

16. Фаза 4 — Затверджэння

Калі React выявляе, што трэба зменіць, ён пераходзіць да этапу фіксацыі, дзе насправды застосоўвае гэтыя змены да рэальнага DOM.

Уявіце сабе элемент кантакту, чыё пазначэнне пасля завершэння гэтага этапу зменяецца з нуля на адин.

У гэты момент внутрашняя структура React і рэальны DOM нарэшце становяцца адпаведнымі.

Этап фіксацыі — это окремы крок, аднародны з этапам выканання:

Render Phase
    ↓
Calculate what should change

Commit Phase
    ↓
Actually apply DOM changes

17. Этап выканання проты этапу фіксацыі

Розумеўшы гэты разлік, ёсць адна з найпашыльнейшых тэм, якіяя спрашваюць у кандыдаў на адзінку.

Этап выканання

У гэтым этапе React:

  • Выканае функцыі компонентаў
  • Выявляе наступную версію інтэрфейсу
  • Выконвае супрацоўку з данымі
  • Выявляе, што трэба зменіць
Component
   ↓
React elements
   ↓
Reconciliation

Этап фіксацыі

У гэтым этапе React:

  • Выконваецца фактычныя мутацыі DOM
  • Апдэюецца ўсе рэферэнсы
  • Выконваюцься эфекты макетавання, якія зв’язаны з гэтым апдэйтом
React result
    ↓
Real DOM

Ёсць спроставаныяе зображэнне прабегу:

             React
               │
               ▼
        ┌──────────────┐
        │ Render Phase │
        └──────┬───────┘
               │
               ▼
        ┌───────────────┐
        │ Commit Phase  │
        └──────┬────────┘
               │
               ▼
            Real DOM

18. Што выканаецца пасля Commit?

Калі фаза commit завершыцца, браузер перэймае кераванне і заўважае, што DOM змяніўся.

Залежна ад таго, што змянілася, браузеру можа знадобіцца працаваць па калькам стэпеняў:

DOM Changes
    ↓
Style Calculation
    ↓
Layout
    ↓
Paint
    ↓
Composite

Не кожная змяна выклікае неабходнасць адбыцца ўсіх гэтых стэпеняў, каб была роўнасць — напрыклад, можа знадобіцца толькі перазначэнне кольору, без неабходнасці повнага перасчытку макета.

Гэта самэ тое месца, дзе власны цыкл жыцця React перадае кераванне практыцы рэндарування браузера, якая была описана раней.

19. useLayoutEffect

useLayoutEffect выклікаецца пасля таго, як React адбыў змены ў DOM, але да таго часу, калі прыстрой перзора можа адразу паказаць новы візуальны фат.

Прырода выклікання выглядае так:

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>
  );
}

Этот хук є правым інструментам, калі вам патрэбна:

  • Апыліць элементы ў DOM
  • З’явіцца текущыя значэння лейауту
  • Здзейсніць візуальныя налаштаванні сінхронна, перш чым корыстнік побачыць ўсё

Адна парада: якщо робота ўнутры useLayoutEffect є важкай, гэта можа спамітліць процес адрасавання візуальнага фату і зробіць інтерфейс медленным.

20. useEffect

useEffect, на адміну, прызначаны для эфектаў, якія не павінны блакаваць процес адрасавання прыстрою перзора.

useEffect(() => {
  fetch("/api/products");
}, []);
Render
   ↓
Commit
   ↓
Browser Paint
   ↓
useEffect

Актуальныя деталі планавання є трохи болей складнымі, чым гэта, але ключовая практычная правіла ёсць:

Выкорыстоўваеце useLayoutEffect толькі тады, калі ўсё павінна выйсці раней, чым браузер адрасавае елементы; для всьога, што можа выконвацца пасля таго, як інтэрфейс вже стаў видным, выкорыстоўваеце useEffect.

21. Полны прыклад

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>
  );
}

Канцэптуальна, последовасць запуску задач выглядае так:

User clicks button
        ↓
setCount()
        ↓
State update scheduled
        ↓
Counter() executes
        ↓
"Render"
        ↓
React reconciles
        ↓
React commits DOM update
        ↓
"Layout Effect"
        ↓
Browser paints updated UI
        ↓
"Effect"

22. Чы выклікае перерэндарынг абяцелі перерэндарынг дзецячага элемента?

Возьмім такі прыклад:

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>
        {count}
      </button>

      <Child />
    </>
  );
}

Калі апдыяецца count:

Parent renders
       ↓
Child is encountered
       ↓
Child may render as part of reconciliation

So,

Parent state update
      ↓
Parent render
      ↓
Child can render

Гэтая працоўнасць є адной з прычын, чаму існуе React.memo як засоб для оптымізацыі.

23. React.memo

Падазроўваюце, што у вас є:

const Child = React.memo(function Child() {
  console.log("Child rendered");

  return <p>Hello</p>;
});

Якщо абяцель перерэндарынгуе, але пропсы, якія пасылаюцца дзецячаму элементу, застаюцца ідэнтычныя па адрасе, React можа зовсамна праігнораваць перерэндарынг таго мемуізаванага элемента.

Ось гэтая ідея візуальна:

Parent update
     ↓
Parent renders
     ↓
React.memo checks Child props
     ↓
Props unchanged
     ↓
Skip Child render

Але пам’ятайце:

React.memo дае вам шлях ад спрытнасці — ён не гарантуе, што компонент ніколі больш не будзе атрыбутаваны.

Змены контэкста, внутранія стан і іншы факторы все ўсё можу змусіць яго адкорэгавацца, незалежна ад мемоізацыі.

24. useMemo і useCallback

Этыя два хукі часта неправильна разумеюцца.

useMemo: Ён мемоізуе вырахаваную значэнне.

const result = useMemo(() => expensiveCalculation(data), [data]);

useCallback: Ён мемоізуе кансэкт функцыі.

const handleClick = useCallback(() => {
  doSomething(id);
}, [id]);

Оба дзеясно ўжыткаваныя у ситуаціях, калі роўнасць кансэкта мае значэнне для ухіленьня ад павтаральной роботы — насуперак усьму, калі перадаюцца пропсы мемоізаваным дзецячым компонентам.

Але ўжыванне іх усюды за замовчаннем часта прыносіць дапэўню сложнасць у ваш код без рэальной падышкі ў спрытнасці.

25. Адрадзіцельне выкананне у React не ўсё роўна адрысаванню элементаў у браузеры

Этот момент варта часткаці яшчэ раз.

Уявіце сабе следуючы компонент:

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>
        {count}
      </button>

      <p>Hello</p>
    </div>
  );
}

Калі змянюецца count:

App renders
     ↓
New React tree created
     ↓
Reconciliation
     ↓
Only count text differs
     ↓
Commit small DOM update
     ↓
Browser rendering

React не обавязкова будзе перабудаваць гэты маркап з нуля:

<div>
  <button>...</button>
  <p>Hello</p>
</div>

26. Вярчыны DOM — правильная канцэпцыя

Часта можна пабачыць тверджэнне, што React выкарыстоўвае Вярчыны DOM, каб прышвыдзіць роботу браузера. Гэта спрасцаванне.

На самай працэ React зберагае внутраній опис UI ў памяці і выкарыстоўвае процэс супаралелення, каб з’ясаваць, што змінілася.

Сам браузер не мае панявы пра Вярчыны DOM. Ён работае толькі з такімі элементамі, як HTML, DOM, CSS і, ў канечнай лініі, пікселі.

Канцэптуальна, працэс выглядае так:

React Component
      ↓
React Element Tree
      ↓
Reconciliation
      ↓
DOM Mutations
      ↓
Browser
      ↓
Pixels

Вярчыны DOM ніколі не ўтвараюцца як рэальны элементы, якія браузер адразу відрасцварвае.

27. Перывыдрашчэванне проты перамальовкі

Это два разныя панявання, якія не трэба сплутваць.

Перывыдрашчэванне у React

Это тады, калі функцыя компонента выканана знову — практычна, Component() запускаецца занова.

Перамальовкі браузером

Это тады, калі браузер перамальоввае пікселі на экране чераз змяну якой-небудзь візуальной характэрыстыкі — це етап Paint.

Таму памятаце: перывыдрашчэванне у React не ўзроўнаважваецца з перамальовкай браузером.

Пэўна можлівасць, што компонент зноў будзе відрасцварвацца, але залишыць тую ж структуру DOM. Навпакі, браузер можа перамальовваць пікселі з прычын, якія не маюць нічынага з відрасцварванням якога-небудзь компонента React.

28. Пераранжаванне проты перамальовкі

Возьмімо такій прыклад: element.style.width = „500px“;

Зменшэнне шыроўны гэтага элемента зазвычай вымушвае пераслікваваць распаковку:

Style
 ↓
Layout
 ↓
Paint
 ↓
Composite

Тепер паравярцімы яго з: element.style.backgroundColor = „red“;

У гэтым случае браузер зазвычай не патрабуе перарахоўваць геаметрычныя параметры занова:

Style
 ↓
Paint
 ↓
Composite

А для певных змян у трансфармацыях чыстаце адкрытасі браузер можа патрабаваць толькі:

Composite

Розумеўце гэтага разліку ёсць важная частка падчас оптымізацыі анімацый чыстаце інтэрфейсоў, якія маюць вялікія выканы.

29. Застосаванне гэтага ў рэальнай сцэнарыі React

Уявіме типовую сторонку товара ў інтернет-магазіне, структураваную так:

Product Page
 ├── Navbar
 ├── Product Gallery
 ├── Product Information
 ├── Add To Cart
 ├── Reviews
 └── Recommendations

Спадзяймося, ўжо корыстувач нажмае кнопку Дадаць у кошык, якая апдэйтуе колькісць у кошыку.

Канцэптуальна, парадоксы змян можа выглядаць так:

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

У добра параджанаванам прыкладзе програмы, такая зміна колькасці тавароў у кошыку не павинна прыводзіць да непатрэбнага перабудоввання всіх элементаў інтэрфейсу. Самэ гэта ёсць прычыной таго, чым так важлівыя рашэння ў паводзе з межамі компанентаў, месцем зберагчы стану, тэхнікамі мемаізацыі і загальной архітэктурой адраджвання.

30. Калі дорога процэс адраджвання стае проблемай карыстоўнасці

function ProductList({ products }) {
  return products.map(product => (
    <ExpensiveProductCard
      key={product.id}
      product={product}
    />
  ));
}

Падазроўваючы, што гэты список адраджвае 10 000 тавароў, а якаясь несувязаная зміна стану спрычыняе непатрэбную роботу ў вельмі большай колькасці компанентаў — карыстоўнасць можа значна паспадзіцца.

Пашырэныя методы кампенсаціі включаюць:

React.memo
      +
Stable props
      +
useMemo/useCallback where justified
      +
Virtualization
      +
Pagination
      +
Code splitting

Для ўсё бол большых списакоў віртуалізацыя часта выказваеся ўсё бол эфектывай:

10000 items
     ↓
Only visible ~20-50 items rendered

31. Як адраджвання взаімаеся з паралельным React

Новыя версіі React маюць болей прыглушаныя можлівасці планавання. React можа надаць прыоритет разным апдэйтам і, калі гэта цяплы, націснуць або адклаць работу з выкарыстоўванням дадзеных.

Ключовая канцэпцыя, якую трэба памятаць:

Render work
    ↓
Can be scheduled
    ↓
Can be interrupted in modern React
    ↓
Eventually committed

Самэ гэтая прычына, чаму пабочныя эфекты не должны знаходзіцца ў логіцы выкарыстоўвання дадзеных компонента:

function Component() {
  fetch("/api/data"); // ❌ Side effect during render

  return <div>Hello</div>;
}

У замяну трэба выкарыстоўваць хук effect або падходячы шаблон запрашання дадзеных. У ідеале фаза выкарыстоўвання дадзеных должна заставацца чыстай.

32. Прычыны чыстага выкарыстоўвання дадзеных

Канцэптуальна компонент React должен вядзець ся як чыстая функція:

UI = f(props, state)

Пры аднаковых пропах і стане ён завжды должен атрадзіць той самы інтерфейс. Напрыклад:

function Greeting({ name }) {
  return <h1>Hello {name}</h1>;
}

Такая поведзенчасць є прыглушанай і безпечной.

Натоўп, выкарыстоўвання чаго-то такога ўнутрь фазы выкарыстоўвання дадзеных є проблематычным:

function Component() {
  localStorage.setItem("x", "1"); // ❌
  fetch("/api");                  // ❌

  return <div>Hello</div>;
}

Это вводзьце паблічных эфектаў у фазу, яюяя React можа запускаць больш чым раз, або планаваць у нечаканных спосабах.

Паблічныя эфекты належаць іншым месцах — у функціях эфектаў, обрабоўчыках змян, або механізмах запрашэння дадзенаў, якія апрантка прысвайчае.

33. Жыцёвы цикл атрыбутавання — Полны ментальны модэль

                    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

Паўтарычныя запитанні пад час адбору

Q1. Чы гэта апдэйт стану негаразда апдэйтуе DOM?

Нята. Апдэйт стану проста планавае задачу для выканання ў React. React атрыбутавае компонент, праконтроўвае выход проты таго, што было раней, і толькі пасля чаго выкарыстоўвае тыя змены DOM, якія насправды неабходны.

setState
  ↓
Render
  ↓
Reconciliation
  ↓
Commit
  ↓
DOM

Q2. Чы кожна атрыбутаванне ведае да апдэйту DOM?

Нята. React можа атрыбутаваць компонент і ўсё ж вырашыць, што атрыбутаваная візуальная структура ідэнтычна той, якая вже ў экране, і ў такім случае ён не зменяе DOM.

Render
  ↓
Compare
  ↓
No meaningful DOM difference
  ↓
No DOM mutation

Q3. Чы гэта перыядражаванне ў React прыводзіць да перамальвання экрана браузерам?

Ня. Прырэштаванне ў React і мальваванне ў браузеры — гэта два незалежныя процесы.

React Render ≠ Browser Paint

Q4. Што значыць супраўжнеча?

Супраўжнеча — гэта спосаб, якім React пароўнае ўсё няўчаса вырахаваныя структуры UI з паказванымі ранейша, каб выявіць, што самэўсёлкі трэба змяніць.

Q5. Што адбываецца на стадіі commit?

Пад час commit React прыкладзае неабходныя змены да сэрвіруючай среды — напрыклад, апдэйтуе рэальныя вузлы DOM у браузеры.

Q6. У чым разліка между useEffect і useLayoutEffect?

Просты спосаб згадаць гэта:

useLayoutEffect
→ after DOM commit, before paint

useEffect
→ after commit and generally after paint

Выкорыстаўце useLayoutEffect, калі вам трэба синхронна вылічыць або скоректаваць лейаут прытаму, перш чым корыстнік побачыць новую картінку. Для большасці парадоксальных эфектаў, якія не трэба блакаваць процес атрыбутавання, выкорыстаўце useEffect.

Q7. Чы гэта спрыяе атрыбутаванню компонента?

Тыповыя прычыны – пачатковая інсталляцыя, апдэйт стану, паўторны атрыбутавання роднічага компонента, змяна параметраў, апдэйт контексту або данні з зовнішняе базы.

Q8. Як можна оптымізаваць атрыбутаванне? [IMP]

Уместа таго, каб за замовчаннем выкорыстоўваць useMemo у всіх месцах, спачатку з’ясавайце, дзе самае большая перашкода. Потым рассмотріце наступныя падходы:

1. Храніце стан поблізу месца, дзе ён выкорыстоўваецца

Утримайцеся ад структуры, у якой стан знаходзіцца на верхнім роўні, а кожны падкомпонент залежыць ад яго:

App
 ↓
Everything depends on state

prefer:

Feature
 ↓
Local state

Лепшае выбаранне — вжыць шчырэйшую пазуху, калі глобальны стан на самай працэ не патрэбен.

2. Выбірачна мемаізацыя дорогіх компанентав

const ProductCard = React.memo(ProductCardComponent);

3. Следзіце за стварэнням об’ектаў, якія наражаюць мемаізацыю на абранне

У замяну на паўтаральнае стварэнне новага об’екта ў тым жа месцы так:

<Child config={{ theme: "dark" }} />

Можа знадобіцца стабільны апуск, якщо Child ўкладзены ў React.memo і ідэнтычнасць мае значэнне — але не рашайце гэтыя проблемы, пакуль яны насправды не стануць проблемай.

4. Віртуалізацыя дужых спісоў

У замяну на адразуве выкананне ўсіх 10 000 вузлаў DOM, выканайце толькі тыя, якія зараз відначаюцца.

5. Адкладзенне завантажэння дорогіх функцыяй

const AdminDashboard = lazy(() => import("./AdminDashboard"));

Гэта зменшае колькасць JavaScript-кода, які павінен быць завантажаны і выкананы з самага пачатку.

6. Выбірайце тэхнікі выканання, якія падтрымваюцься браузерамі

Калі час, выкарыстоўваюце дышэвыя атрыбуты, такія як transform і opacity, і ухіліцеся ад прымусовых дорогіх перасчыткаў макета ў кожным кадре.