React

View Transitions и устройство Fiber под капотом

При переходе между страницами карточка товара должна плавно увеличиться до полноэкранного вида, а не просто исчезнуть и появиться. Раньше такие анимации писали вручную: измеряли позиции, считали дельты, дёргали CSS-трансформы в нужный момент. React предлагает экспериментальный компонент ViewTransition (react@canary) - обернул переход, и React сам анимирует разницу между состояниями. Чтобы понять, как это вообще возможно, нужно заглянуть под капот: в архитектуру Fiber, разделение на фазы render и commit и двойную буферизацию деревьев. Это завершающий урок - здесь сходится всё, что курс собирал по частям.

  • Анимированные переходы между маршрутами: карточка разворачивается в страницу, список перестраивается плавно
  • Оптимистичные изменения списка с анимацией добавления и удаления элементов через ViewTransition
  • Конкурентный рендеринг: прерываемая фаза render позволяет не блокировать ввод на тяжёлых обновлениях
  • Серверный рендеринг и гидрация: статичный HTML с сервера оживает, когда Fiber присоединяет к нему обработчики
  • React DevTools и Performance Tracks показывают именно фазы Fiber - render и commit на временной шкале

Предварительные знания

  • Reconciliation: сравнение нового дерева элементов со старым
  • Конкурентный рендеринг: прерываемость и приоритеты обновлений
  • Серверный рендеринг и идея гидрации статичного HTML
  • Reconciliation
  • Введение в конкурентность

От стекового reconciler к Fiber и обратно к анимациям

До React 16 reconciler был рекурсивным: обход дерева шёл по стеку вызовов и не мог прерваться, поэтому большое обновление блокировало главный поток. Эндрю Кларк и Себастьян Маркбоге переписали движок как Fiber (React 16, 2017) - каждый узел стал объектом-fiber с указателями на родителя, ребёнка и соседа, а обход превратился в цикл, который можно остановить и продолжить. Эта прерываемость и сделала возможным конкурентный рендеринг (React 18) и use(). В 19.2 поверх той же архитектуры вышли Performance Tracks, а ViewTransition остаётся экспериментальным (react@canary): зная фазы commit, React точно знает момент, когда снять снимок до и применить разницу после.

ViewTransition: анимация разницы состояний

Анимация перехода между двумя состояниями интерфейса вручную это морока: нужно поймать старые позиции элементов, посчитать, куда они переедут, и проиграть трансформацию в нужный момент. Экспериментальный компонент ViewTransition (react@canary) берёт это на себя. Изменение, которое его затрагивает, React оборачивает в переход: снимает состояние до, применяет новое и анимирует разницу между ними.

Анимация запускается, когда обновление помечено как переходное через startTransition. React в фазе commit снимает снимок прежнего DOM, применяет новый, и браузерный механизм View Transitions API анимирует переход от снимка к новому виду. Разработчик описывает что меняется, а как анимировать разницу решает React поверх платформенного API.

ViewTransition опирается на нативный View Transitions API браузера. React добавляет к нему свою ценность: он точно знает момент фазы commit, когда DOM меняется, и именно туда вставляет снятие снимка до и применение состояния после. Без знания фаз Fiber такая интеграция была бы неточной.

Анимация перехода это не повод анимировать всё подряд. ViewTransition уместен для осмысленных смен состояния (раскрытие карточки, смена маршрута, перестроение списка). Избыточная анимация мелких обновлений утомляет и крадёт ощущение скорости вместо того, чтобы его добавлять.

Что делает компонент ViewTransition при изменении состояния?

Архитектура Fiber и рабочий цикл

Fiber это две вещи сразу: структура данных и алгоритм. Каждому узлу дерева соответствует объект-fiber, хранящий тип элемента, пропсы, состояние и указатели на родителя (return), первого ребёнка (child) и следующего соседа (sibling). Эти указатели превращают дерево в структуру, которую можно обходить итеративно, без рекурсии по стеку вызовов.

Рабочий цикл обходит fiber-узлы по этим указателям: спускается к ребёнку, затем к соседу, затем поднимается к родителю. Поскольку это цикл, а не рекурсия, React может остановиться между узлами, отдать управление браузеру (например, ради обработки ввода) и продолжить с того же места. Эта прерываемость и есть фундамент конкурентного рендеринга.

На каждом узле в фазе beginWork React вычисляет новый результат рендера и сравнивает с прежним (reconciliation), помечая, что изменилось. В фазе completeWork при подъёме формируются эффекты - список того, что нужно сделать с реальным DOM. Важно: пока идёт этот обход, реальный DOM не трогается вообще. Все изменения лишь накапливаются.

Указатель alternate связывает fiber с его парой в другом дереве. Именно через alternate React переиспользует узлы между обновлениями, не создавая всё дерево заново, и реализует двойную буферизацию, к которой переходим дальше.

Почему обход дерева в Fiber устроен как прерываемый цикл, а не как рекурсия?

Фазы render и commit, двойная буферизация и гидрация

Работа Fiber делится на две принципиально разные фазы. Render-фаза прерываемая и не имеет побочных эффектов: React строит и сравнивает деревья, вычисляя, что должно измениться. Поскольку она не трогает DOM, её можно остановить, выбросить и начать заново без видимых последствий. Commit-фаза синхронная и неделимая: она за один проход применяет накопленные изменения к реальному DOM, запускает layout-эффекты и обновляет ref.

  • Render-фаза — Прерываемая, без побочных эффектов. Строит workInProgress, сравнивает с current, помечает изменения. Может быть отброшена и пересчитана.
  • Commit-фаза — Синхронная, неделимая. Применяет изменения к DOM за один проход, запускает эффекты, меняет указатель current. Прервать нельзя.

В основе лежит двойная буферизация - приём из компьютерной графики. React держит два дерева fiber. Дерево current отражает то, что сейчас на экране. Новое дерево workInProgress строится параллельно, в стороне, на основе current через указатели alternate. Пользователь всё это время видит стабильный current. Когда workInProgress готов, в фазе commit React одним движением переключает указатель: workInProgress становится current. Никаких полупостроенных состояний на экране не бывает.

Гидрация это особый случай первого commit поверх готового HTML. При серверном рендеринге браузер уже получил статичную разметку. Вместо построения DOM с нуля Fiber строит дерево и присоединяет его к существующим узлам: сопоставляет fiber с реальными элементами и навешивает обработчики событий и состояние. DOM не пересоздаётся, поэтому пользователь видит контент мгновенно, а интерактивным он становится по мере гидрации.

Здесь всё сходится. Прерываемая render-фаза (наследие Fiber) даёт конкурентность и приоритеты. Чёткая граница commit-фазы даёт точку, куда ViewTransition вставляет снимок до и состояние после для анимации. Двойная буферизация гарантирует, что на экране всегда целостное дерево. А гидрация связывает серверный HTML с клиентским Fiber. Это и есть устройство React под капотом - от первого урока про UI = f(state) до анимаций и internals.

Деление на прерываемый render без эффектов и неделимый commit с эффектами объясняет известное правило: функция компонента и render-логика обязаны быть чистыми. React вправе вызвать render несколько раз и отбросить результат, поэтому побочные эффекты место только в commit-фазе - в эффектах, а не в теле компонента.

Зачем React использует двойную буферизацию с деревьями current и workInProgress?

Связь с другими темами

Этот завершающий урок связывает всё устройство React воедино:

  • Reconciliation — Алгоритм сравнения деревьев, который исполняет рабочий цикл Fiber в фазе render
  • Конкурентность — Прерываемость фазы render и приоритеты обновлений это прямое следствие архитектуры Fiber

Итог

  • Компонент ViewTransition оборачивает изменение UI, и React анимирует разницу между состояниями до и после, без ручного расчёта позиций
  • Fiber это объект на каждый узел дерева плюс рабочий цикл, который обходит узлы и может прерываться на тяжёлых обновлениях
  • Работа делится на две фазы: render (прерываемая, без побочных эффектов, считает изменения) и commit (синхронная, применяет их к DOM)
  • Двойная буферизация держит два дерева: current отражает экран, а workInProgress строится в стороне и становится current в commit
  • Гидрация присоединяет дерево Fiber к готовому серверному HTML, навешивая обработчики без повторного построения DOM
  • ViewTransition опирается на точное знание фазы commit: момент до даёт снимок, момент после - конечное состояние для анимации

Связанные уроки

  • rc-17-reconciliation — Fiber это движок, исполняющий reconciliation. Понимание сравнения деревьев предшествует разбору его устройства
  • rc-24-concurrent-intro — Прерываемый render и приоритеты обновлений из конкурентной модели лежат в основе рабочего цикла Fiber
View Transitions и устройство Fiber под капотом

0

1

Войти