Пошук уроків, статей та іншого контенту
Розберемо Fiber як внутрішній механізм React для планування, переривання та пріоритизації роботи рендерингу.
Fiber — це внутрішній механізм React, який представляє компоненти та їхню роботу у вигляді окремих задач. Він дає React змогу:
розбивати рендеринг на невеликі частини;
призупиняти незавершену роботу;
продовжувати її пізніше;
скасовувати застарілий результат;
визначати пріоритет різних оновлень;
окремо виконувати підготовку змін і застосування змін до DOM.
До Fiber React переважно виконував оновлення дерева синхронно: почавши рендеринг, він мав пройти всю необхідну структуру до кінця. Велика операція могла надовго заблокувати головний потік браузера.
Fiber змінив внутрішню модель роботи React. Замість одного великого завдання React працює з набором менших одиниць роботи.
Fiber — це не окремий публічний API, а внутрішня реалізація React.
Кожен змонтований елемент React має внутрішнє представлення — Fiber-вузол.
Fiber-вузол містить інформацію, необхідну React для роботи з конкретним компонентом або елементом:
тип компонента;
його props;
поточний стан;
посилання на дочірні вузли;
посилання на сусідні вузли;
посилання на батьківський вузол;
інформацію про заплановані зміни;
пріоритет роботи.
Спрощено дерево Fiber можна уявити так:
App
└── Page
├── Header
└── List
├── ListItem
├── ListItem
└── ListItemУ реальному React кожен вузол має більше внутрішніх полів, але важливий сам принцип: React обробляє дерево не як одну нерозривну операцію, а як набір пов'язаних одиниць роботи.
Під час оновлення React зазвичай працює з двома версіями дерева:
поточним деревом, яке вже відповідає відображенню;
новим деревом, яке React будує під час наступного рендерингу.
Нове дерево називають work-in-progress tree — деревом роботи в процесі.
React може поступово будувати це дерево, не змінюючи DOM після кожного обробленого вузла. Коли підготовка завершена, React застосовує результат до DOM.
Це дозволяє не показувати користувачу напівготовий результат.
Роботу Fiber зручно розглядати через дві основні фази.
На цій фазі React:
визначає, які компоненти потрібно повторно обчислити;
викликає їхні функції або методи рендерингу;
порівнює отриманий результат із попереднім;
будує нову структуру Fiber;
збирає інформацію про майбутні зміни.
Ця фаза може бути:
перерваною;
продовженою пізніше;
виконаною повторно;
покинутою, якщо з'явилося новіше оновлення.
Тому код під час render-фази має бути чистим. Він не повинен виконувати побічні ефекти, наприклад:
змінювати DOM вручну;
відправляти HTTP-запити;
змінювати зовнішні змінні;
реєструвати підписки.
Для побічних ефектів використовують відповідні механізми, зокрема useEffect.
На цій фазі React застосовує підготовлені зміни:
додає або видаляє DOM-вузли;
змінює атрибути;
оновлює текст;
запускає ефекти в потрібний момент.
Commit-фаза має бути узгодженою: користувач не повинен побачити стан, у якому змінено лише половину дерева.
Важливо розрізняти:
render — React обчислює, що потрібно змінити;
commit — React фактично застосовує зміни.
Виклик функції компонента ще не означає, що DOM уже змінився.
Fiber дає React змогу розглядати обробку дерева як послідовність невеликих кроків.
У спрощеному вигляді React робить приблизно таке:
1. Взяти наступний Fiber-вузол.
2. Виконати необхідну роботу для нього.
3. Перейти до дочірнього вузла.
4. Якщо дочірнього вузла немає — перейти до сусіднього.
5. Якщо сусіднього немає — повернутися до батьківського.
6. Перевірити, чи потрібно поступитися головним потоком.
7. Продовжити або зупинитися до наступного моменту.Реальна реалізація складніша, але принцип саме такий: дерево обходиться частинами, а не обов'язково одним безперервним викликом.
Браузер має один головний потік, на якому виконуються JavaScript, обробка подій і значна частина роботи з інтерфейсом.
Якщо React надовго займає цей потік, інтерфейс може:
перестати реагувати на натискання;
втрачати введені символи;
прокручуватися ривками;
пропускати кадри анімації.
Fiber дає React можливість не виконувати всю підготовчу роботу безперервно. Якщо з'явилася більш важлива задача, React може:
призупинити поточний render;
обробити важливіше оновлення;
повернутися до попередньої роботи;
або повністю відкинути її, якщо вона вже не актуальна.
Переривання відбувається під час render-фази. Уже розпочату commit-фазу React не розбиває довільним чином, оскільки DOM має перейти з одного узгодженого стану в інший.
Не всі оновлення однаково важливі для користувача.
Наприклад:
введення символу в поле пошуку має швидко відображатися;
фільтрація великого списку може виконуватися трохи пізніше;
оновлення, яке не видно зараз, може мати ще нижчий пріоритет.
Сучасний React використовує внутрішню систему пріоритетів, яку реалізаційно представляють lanes. Lane можна розглядати як категорію роботи з певним пріоритетом.
Зазвичай розробник не працює з lanes напряму. React сам визначає пріоритет у багатьох ситуаціях, а публічний API startTransition дозволяє позначити оновлення як менш термінове.
startTransitionУ наступному прикладі введення в поле має високий пріоритет, а фільтрація великого списку позначена як перехідна робота.
import { useMemo, useState, useTransition } from "react";
const products = Array.from({ length: 5000 }, (_, index) => ({
id: index + 1,
name: `Product ${index + 1}`,
}));
export default function App() {
const [inputValue, setInputValue] = useState("");
const [query, setQuery] = useState("");
const [isPending, startTransition] = useTransition();
const filteredProducts = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();
if (!normalizedQuery) {
return products;
}
return products.filter((product) =>
product.name.toLowerCase().includes(normalizedQuery)
);
}, [query]);
function handleChange(event) {
const nextValue = event.target.value;
// Введене значення має оновитися без затримки.
setInputValue(nextValue);
startTransition(() => {
// Фільтрація може бути виконана після терміновіших оновлень.
setQuery(nextValue);
});
}
return (
<main>
<h1>Пошук товарів</h1>
<label>
Пошуковий запит:
<input
value={inputValue}
onChange={handleChange}
placeholder="Введіть назву"
/>
</label>
{isPending && <p>Оновлюємо список…</p>}
<p>Знайдено: {filteredProducts.length}</p>
<ul>
{filteredProducts.slice(0, 100).map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</main>
);
}У цьому прикладі:
inputValue змінюється як термінове оновлення;
query змінюється всередині startTransition;
React може спочатку забезпечити швидке оновлення поля;
обчислення і відображення нового списку можуть мати нижчий пріоритет;
isPending повідомляє, що перехідна робота ще триває.
startTransition не створює окремий потік і не прискорює саме фільтрування. Він повідомляє React, що це оновлення можна перервати заради важливішої роботи.
Також важливо: якщо обчислення повністю блокує JavaScript на дуже довгий час, Fiber не може магічно розділити один синхронний цикл JavaScript усередині функції. React може переривати свою роботу між одиницями Fiber, але довгий звичайний цикл у компоненті все одно блокує головний потік.
Розглянемо ситуацію:
Користувач вводить r.
React починає готувати список для r.
Користувач майже одразу вводить re.
Для re з'являється новіше оновлення.
Результат для r може вже не мати сенсу. React не зобов'язаний завершувати і застосовувати цю роботу. Він може:
перервати її;
відкинути підготовлене дерево;
почати або продовжити роботу для re;
застосувати лише актуальний результат.
Саме тому render-фаза має бути без побічних ефектів. React може викликати компонент більше одного разу, а деякі обчислення можуть не дійти до commit-фази.
Fiber не означає, що React оновлює кожен компонент при кожній зміні. React спочатку визначає, яку частину дерева потрібно обробити.
На це впливають:
джерело оновлення;
зміна props;
зміна стану;
контекст;
мемоізація;
пріоритет роботи.
Якщо компонент потрібно обробити, React викликає його під час render-фази та отримує нове описання UI. Після цього React порівнює результат із попереднім і готує мінімально необхідні зміни.
Fiber допомагає керувати цією роботою, але сам по собі не гарантує, що компонент не буде повторно викликаний. Компоненти мають залишатися чистими й не покладатися на кількість викликів функції.
У контексті React слово «паралельний» може бути оманливим. React не запускає компоненти в окремих JavaScript-потоках.
Йдеться про інше:
робота може бути перервана;
різні оновлення можуть мати різні пріоритети;
React може не застосовувати застарілий результат;
браузер може отримати можливість обробити інші задачі між частинами роботи.
Точніше називати це переривним або конкурентним плануванням роботи, а не виконанням у кількох потоках.
У більшості випадків із Fiber не потрібно взаємодіяти напряму. Однак його принципи пояснюють важливі правила React:
render-фаза повинна бути чистою;
не слід виконувати побічні ефекти під час обчислення JSX;
commit-фаза може відбутися пізніше, ніж виклик компонента;
компонент може бути обчислений повторно;
застаріла робота може бути відкинута;
термінові й нетермінові оновлення можна розділяти;
складні обчислення в компоненті все одно можуть блокувати головний потік.
Спрощено оновлення React можна описати так:
Оновлення стану або props
↓
React планує роботу
↓
Render-фаза
- виклик компонентів
- побудова нового Fiber-дерева
- порівняння з попереднім деревом
- можливе переривання
↓
Commit-фаза
- застосування змін до DOM
- виконання відповідних ефектів
↓
Оновлений інтерфейсЯкщо під час render-фази виникає важливіше оновлення, попередня робота може бути призупинена або відкинута. Якщо робота дійшла до commit-фази, React застосовує узгоджений результат.
Fiber працює в межах JavaScript-моделі браузера. Він не створює новий потік для компонентів.
Функція компонента лише повертає опис UI. Зміни DOM застосовуються пізніше, під час commit-фази.
Оскільки render може бути повторений або перерваний, побічний ефект може виконатися кілька разів або для результату, який не буде застосований.
startTransition прискорює важке обчисленняstartTransition змінює пріоритет оновлення. Він не робить саме обчислення швидшим і не переносить його в інший потік.
React може перервати свою роботу між обробкою одиниць Fiber. Але довга синхронна функція або великий цикл усередині JavaScript усе одно можуть заблокувати головний потік.
Кількість викликів функції компонента не є стабільним способом визначити кількість фактичних оновлень DOM. Render і commit — різні фази.
Fiber — внутрішня структура React для представлення компонентів і керування роботою рендерингу.
React може розбивати render-фазу на менші одиниці роботи.
Render-фаза може бути перервана, повторена або відкинута.
Commit-фаза застосовує узгоджені зміни до DOM.
React використовує пріоритети, щоб термінові оновлення не чекали на великі другорядні обчислення.
startTransition дозволяє позначити оновлення як менш термінове.
Fiber не створює окремі потоки й не робить довгий синхронний код автоматично швидшим.
Чистий render без побічних ефектів — необхідна умова коректної роботи з переривним рендерингом.