React Front-end Инженер

React Front-end Инженер

Роадмап навыков для прокачки

Как работает алгоритм Fiber (объяснение своими словами)

ReactReconciliationFiber

Основная идея

Fiber — это алгоритм reconciliation в React, который позволяет разбивать рендеринг на небольшие порции работы и распределять их по нескольким кадрам. Это делает интерфейс отзывчивым даже при сложных обновлениях.

Ключевые аспекты

  • Инкрементальный рендеринг — работа делится на маленькие части (units of work)
  • Приоритезация — срочные обновления (анимации, клики) выполняются раньше
  • Прерываемость — React может приостановить работу и вернуться к ней позже
  • Две фазы — render (можно прервать) и commit (нельзя прервать)
  • Linked list — Fiber-ноды связаны через child, sibling, return

Как это работает

  1. React создаёт Fiber-дерево из компонентов
  2. При обновлении обходит дерево порциями
  3. Между порциями проверяет, нужно ли уступить главному потоку
  4. Накапливает изменения в памяти
  5. Применяет все изменения к DOM за один раз (commit)

Частые ошибки на собеседованиях

  • Путают Fiber (архитектура) с Virtual DOM (концепция) — Fiber это реализация работы с Virtual DOM
  • Думают, что Fiber делает React многопоточным — React всё ещё однопоточный
  • Не понимают разницу между render и commit фазами
  • Забывают, что прерываемость работает только в Concurrent Mode

Введение и проблематика

Проблема старого алгоритма Stack Reconciler

До React 16 использовался Stack Reconciler — алгоритм, который обрабатывал обновления рекурсивно и синхронно. Это создавало серьёзную проблему: если дерево компонентов было большим, браузер "зависал" на время обработки.

js
// Представьте дерево из 10 000 компонентов
// Stack Reconciler обрабатывал бы их все за один проход
// Браузер не мог бы отрисовать анимацию или обработать клик

Главный поток браузера отвечает и за JavaScript, и за отрисовку UI. Если JS занят больше 16мс, пользователь видит "лаги".

Зачем нужен Fiber

Fiber решает эту проблему, разбивая работу на маленькие порции. React может:

  • Приостановить работу и вернуться к ней позже
  • Назначать приоритеты разным типам обновлений
  • Переиспользовать ранее выполненную работу
  • Отменять работу, если она больше не нужна

Базовая теория

Что такое Fiber-нода

Fiber-нода — это JavaScript-объект, который представляет единицу работы. Для каждого React-элемента создаётся соответствующая Fiber-нода.

Code Example 1: Что представляет собой Fiber-нода? Для чего нужны свойства child, sibling, return?

ts
// Упрощённая структура Fiber-ноды
interface Fiber {
  // Тип компонента
  type: any;
  key: string | null;
 
  // Связи с другими нодами (linked list)
  child: Fiber | null;      // первый ребёнок
  sibling: Fiber | null;    // следующий брат
  return: Fiber | null;     // родитель
 
  // Состояние
  pendingProps: any;        // новые props
  memoizedProps: any;       // props после рендера
  memoizedState: any;       // state после рендера
 
  // Эффекты
  flags: Flags;             // какие операции нужны (Placement, Update, Deletion)
 
  // Для работы планировщика
  lanes: Lanes;             // приоритет обновления
}

Linked List вместо стека

В отличие от рекурсии, связный список позволяет "запомнить" где остановились:

graph TD A[App] --> B[Header] A --> C[Main] A --> D[Footer] C --> E[Sidebar] C --> F[Content] style A fill:#e1f5fe style C fill:#fff3e0

Обход происходит так:

  1. Спуск к первому ребёнку (child)
  2. Если нет детей — переход к брату (sibling)
  3. Если нет братьев — возврат к родителю (return)

Две фазы работы

Render Phase (Reconciliation)

  • Можно прервать и возобновить
  • Выполняется "в памяти"
  • Вычисляет, какие изменения нужны
  • Вызывает render(), shouldComponentUpdate()

Code Example 2: Как работает функция performUnitOfWork? Почему используется связный список вместо рекурсии?

js
// React обходит дерево и вычисляет различия
function performUnitOfWork(fiber) {
  // 1. Выполнить работу для этой ноды
  beginWork(fiber);
 
  // 2. Если есть ребёнок — вернуть его как следующую работу
  if (fiber.child) {
    return fiber.child;
  }
 
  // 3. Иначе — завершить работу и перейти к брату/родителю
  completeUnitOfWork(fiber);
}

Практический пример работы алгоритма

Шаг 1: Создание Fiber-дерева

При первом рендере React создаёт Fiber-дерево из JSX:

Code Example 4: Какое Fiber-дерево создаст React для этого компонента?

jsx
function App() {
  return (
    <div>
      <Header />
      <Content />
    </div>
  );
}
// Создаются Fiber-ноды: App → div → Header, Content

Шаг 2: Обновление состояния

Когда вызывается setState, React планирует обновление:

jsx
const [count, setCount] = useState(0);
// setCount(1) создаёт "update" объект и планирует работу

Шаг 3: Work Loop

Планировщик запускает цикл работы:

Code Example 5: Как workLoop позволяет браузеру обрабатывать другие задачи? Что такое time slicing?

js
function workLoop(deadline) {
  // Пока есть работа и время
  while (nextUnitOfWork && deadline.timeRemaining() > 0) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
  }
 
  // Если время вышло — уступаем браузеру
  if (nextUnitOfWork) {
    requestIdleCallback(workLoop);
  }
}

Шаг 4: Commit

После завершения render phase, все изменения применяются за один раз.


Приоритеты обновлений

React разделяет обновления по приоритетам:

ПриоритетПримерПоведение
ImmediateКлик, ввод текстаВыполняется сразу
User-blockingHover эффектыВысокий приоритет
NormalЗагрузка данныхСтандартный приоритет
LowАналитикаМожет откладываться
IdleПредзагрузкаВыполняется в свободное время
⚠️

Приоритезация полностью работает только в Concurrent Mode (React 18+). В Legacy Mode все обновления синхронные.


Double Buffering

React использует технику "двойной буферизации":

text
Current Tree          Work-in-Progress Tree
(то, что на экране)   (новая версия)
     ↓                      ↓
  [Fiber]  ←──alternate──→ [Fiber]
     ↓                      ↓
  [Fiber]  ←──alternate──→ [Fiber]
  • Current — дерево, которое сейчас отображается
  • Work-in-Progress — дерево, которое строится
  • После commit они меняются местами

Пограничные кейсы

⚠️

Важно: Render phase может выполняться несколько раз для одного обновления! Не делайте side effects в render.

Code Example 6: Почему side effects в render — это проблема в Concurrent Mode? Как исправить этот код?

jsx
// ❌ Неправильно — side effect в render
function Component() {
  console.log('render'); // может вызваться несколько раз
  sendAnalytics();       // опасно!
 
  return <div>...</div>;
}
 
// ✅ Правильно — side effects в useEffect
function Component() {
  useEffect(() => {
    sendAnalytics(); // вызовется один раз после commit
  }, []);
 
  return <div>...</div>;
}

Плюсы и минусы

АспектStack ReconcilerFiber
Отзывчивость❌ Блокирует UI✅ Прерываемый
Приоритеты❌ Нет✅ Есть
Сложность✅ Простой❌ Сложный
Память✅ Меньше❌ Больше (два дерева)
Concurrent features❌ Нет✅ Suspense, Transitions

Вопросы интервьюера

Q: Почему React может вызвать render несколько раз?

В Concurrent Mode React может прервать render phase и начать заново, если пришло более приоритетное обновление. Поэтому render должен быть чистой функцией.

Q: Что такое "time slicing"?

Техника разбиения работы на кусочки по ~5мс, чтобы не блокировать главный поток. Между кусочками браузер может обрабатывать события и рисовать.

Q: Как Fiber связан с Suspense?

Fiber позволяет "приостановить" рендеринг компонента, ожидая данные. Без прерываемости Suspense был бы невозможен.

Q: Все ли обновления прерываемые?

Нет. flushSync() делает обновление синхронным. Также в Legacy Mode все обновления синхронные.


Источники

Code Example 1: Fiber node structure

❓ Что представляет собой Fiber-нода? Для чего нужны свойства child, sibling, return?

ts
interface Fiber {
  type: any;
  key: string | null;
 
  child: Fiber | null;
  sibling: Fiber | null;
  return: Fiber | null;
 
  pendingProps: any;
  memoizedProps: any;
  memoizedState: any;
 
  flags: Flags;
  lanes: Lanes;
}

Code Example 2: Render phase work loop

❓ Как работает функция performUnitOfWork? Почему используется связный список вместо рекурсии?

js
function performUnitOfWork(fiber) {
  beginWork(fiber);
 
  if (fiber.child) {
    return fiber.child;
  }
 
  completeUnitOfWork(fiber);
}

Code Example 3: Commit phase

❓ Что происходит в каждой из трёх подфаз commit? Почему commit нельзя прервать?

js
function commitRoot(root) {
  commitBeforeMutationEffects();
 
  commitMutationEffects();
 
  commitLayoutEffects();
}

Code Example 4: Component tree to Fiber

❓ Какое Fiber-дерево создаст React для этого компонента?

jsx
function App() {
  return (
    <div>
      <Header />
      <Content />
    </div>
  );
}

Code Example 5: Work loop with time slicing

❓ Как workLoop позволяет браузеру обрабатывать другие задачи? Что такое time slicing?

js
function workLoop(deadline) {
  while (nextUnitOfWork && deadline.timeRemaining() > 0) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
  }
 
  if (nextUnitOfWork) {
    requestIdleCallback(workLoop);
  }
}

Code Example 6: Side effects in render problem

❓ Почему side effects в render — это проблема в Concurrent Mode? Как исправить этот код?

Problem:

jsx
function Component() {
  console.log('render');
  sendAnalytics();
 
  return <div>...</div>;
}

Solution:

jsx
function Component() {
  useEffect(() => {
    sendAnalytics();
  }, []);
 
  return <div>...</div>;
}