React

Компонент Activity

Многошаговая форма: пользователь заполняет шаг, переключается на следующий, возвращается - и всё введённое пропало. Причина типична: неактивный шаг условно размонтировали через тернарник, а вместе с DOM исчезло и его локальное состояние. Привычные обходы - тащить состояние всех шагов наверх или прятать неактивные через display: none, теряя выигрыш производительности. React 19.2 предлагает прямое решение: компонент Activity, который умеет держать поддерево в скрытом режиме - не показывая его, но сохраняя его состояние и даже пред-рендеря заранее.

  • Многошаговые мастера и табы: скрытые шаги сохраняют введённые данные и позицию скролла при возврате
  • Пред-рендеринг вероятного следующего экрана: подготовить контент по ссылке, на которую пользователь, скорее всего, нажмёт
  • Тяжёлые панели дашборда: скрыть неактивную панель, не размонтируя и не теряя её состояние
  • Замена паттерна display: none: Activity скрывает поддерево, но дополнительно деприоритизирует его рендеринг
  • React 19.2 (октябрь 2025): Activity вышел как часть конкурентного набора рядом с useEffectEvent (ViewTransition пока экспериментальный)

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

  • Конкурентная модель: рендер прерываем и приоритизируем, работу можно выполнять с низким приоритетом
  • Жизненный цикл компонента: что значит монтирование, размонтирование и потеря локального состояния
  • Эффекты: что useEffect запускается при монтировании и очищается при размонтировании

Зачем понадобился Activity

Проблема сохранения состояния скрытых частей дерева стара. Разработчики годами выбирали из плохих вариантов: условный рендер терял состояние при размонтировании, а display: none сохранял DOM, но React всё равно держал поддерево полностью активным - с работающими эффектами и без деприоритизации. Команда React долго прорабатывала концепцию под рабочими названиями Offscreen, затем Activity. Идея в том, чтобы дать первоклассный способ объявить часть UI 'сейчас скрытой, но живой'. В React 19.2 (октябрь 2025) Activity вышел стабильно: он позволяет хранить состояние скрытого поддерева, останавливать его эффекты и пред-рендерить будущий UI с низким приоритетом, не мешая видимой части.

Проблема условного размонтирования

Самый распространённый способ скрыть часть UI - условный рендер: если вкладка неактивна, не рендерить её вовсе. Но когда поддерево перестаёт рендериться, React его размонтирует, а вместе с ним исчезает локальное состояние компонентов, позиция скролла, фокус и результат уже выполненных эффектов. При возврате всё строится заново с чистого листа.

Исторические обходы решали задачу лишь частично. Поднять состояние всех шагов в общий стор спасает данные, но раздувает родителя и ломает инкапсуляцию. Прятать неактивное через display: none сохраняет и DOM, и состояние, но у этого приёма своя цена: React продолжает считать скрытое поддерево полностью активным - его эффекты работают, а его рендеры конкурируют за приоритет с видимой частью.

  • Условный рендер — Скрытое поддерево размонтируется. Память освобождается, но состояние, скролл и фокус теряются. Возврат - с нуля.
  • display: none — DOM и состояние сохраняются, но React держит поддерево активным: эффекты работают, рендеры не деприоритизированы.

Таким образом, до Activity не было способа сказать React: 'это поддерево сейчас не видно, поэтому сохрани его состояние, но не трать на него ресурсы как на видимое'. Приходилось выбирать между потерей состояния и потерей контроля над приоритетом и эффектами.

Почему условный рендер через тернарник теряет введённые данные неактивного шага?

Activity: режимы visible и hidden

Activity оборачивает поддерево и принимает проп mode со значением visible или hidden. В режиме visible поддерево ведёт себя обычно. В режиме hidden React не показывает его на экране, но сохраняет состояние всех компонентов внутри - введённые значения, скролл, локальные данные. При возврате в visible поддерево появляется ровно таким, каким было, без повторного монтирования с нуля.

Ключевое отличие от display: none - управление эффектами и приоритетом. Когда Activity переходит в hidden, React очищает (останавливает) эффекты скрытого поддерева, как при размонтировании, но без уничтожения состояния. При возврате в visible эффекты запускаются снова. Так подписки и таймеры скрытой части не работают впустую, а состояние при этом не теряется.

Из-за остановки эффектов в hidden код должен корректно переживать цикл 'эффект очищен - эффект запущен снова' без потери смысла. Это та же дисциплина, что и при обычном монтировании и размонтировании: правильно настроенные cleanup-функции. Поддеревья с эффектами, которые нельзя безопасно останавливать и перезапускать, требуют осторожности.

На момент React 19.2 API экспортируется с префиксом unstable_ - это сигнал, что детали могут уточняться. Но концепция стабильна и рекомендована: объявлять часть UI скрытой, но живой, вместо ручного жонглирования размонтированием и поднятым состоянием.

Чем Activity в режиме hidden отличается от простого скрытия через display: none?

Пред-рендеринг вероятного UI

Второе применение Activity - не сохранять прошлое, а готовить будущее. Поддерево в режиме hidden можно отрендерить заранее, до того как пользователь его откроет. React выполняет этот пред-рендер с низким приоритетом, не мешая видимой части - ровно по конкурентной модели. Когда пользователь переключается, скрытое поддерево уже готово, и переход ощущается мгновенным.

Это особенно мощно в связке с Suspense и загрузкой данных. Пока пользователь смотрит ленту, скрытый профиль в фоне успевает не только отрендериться, но и подтянуть свои данные через Suspense. К моменту перехода и код, и данные на месте, и пользователь не видит ни спиннера, ни паузы. По сути это контролируемый префетч целого куска UI вместе с его состоянием.

  • Сохранение состояния: скрытые шаги, табы и панели не теряют введённое и позицию скролла
  • Деприоритизация: рендер и пред-рендер скрытой части не конкурируют с видимой за основной поток
  • Пред-рендеринг: вероятный следующий экран готовится заранее, делая переход мгновенным
  • Управление эффектами: подписки и таймеры скрытой части останавливаются, а не работают впустую

Activity стоит применять там, где сохранение состояния или мгновенность перехода реально важны: мастера, табы, навигация по вероятным маршрутам. Оборачивать в него всё подряд не нужно - удержание скрытых поддеревьев в памяти не бесплатно. Как и прочие конкурентные инструменты, это точечная оптимизация под конкретный UX-сценарий, а не настройка по умолчанию.

Как Activity позволяет сделать переход на следующий экран мгновенным?

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

Activity завершает блок конкурентности, опираясь на его модель:

  • Идея конкурентности — Пред-рендер скрытого поддерева идёт с низким приоритетом по той же модели срочное/несрочное
  • Порталы и рефы — Другой способ управлять поддеревом вне обычного потока, но через место в DOM, а не сохранение состояния
  • lazy и Suspense — Дополняют Activity: пред-рендерить можно сегмент, чьи данные подтягиваются через Suspense

Итог

  • Activity - компонент React 19.2 с режимами visible и hidden, оборачивающий поддерево, состояние которого нужно сохранить
  • В режиме hidden поддерево не показывается, но его состояние сохраняется, а эффекты останавливаются до возврата в visible
  • Это замена условному размонтированию: вместо потери состояния через тернарник поддерево остаётся живым, но скрытым
  • Activity умеет пред-рендерить вероятный следующий UI с низким приоритетом, чтобы переключение ощущалось мгновенным
  • Отличие от display: none в том, что React не просто прячет DOM, а деприоритизирует рендер и управляет эффектами скрытой части

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

  • rc-24-concurrent-intro — Activity пред-рендерит скрытое поддерево с низким приоритетом, опираясь на конкурентную модель приоритетов
  • rc-23-portals-refs-advanced — Тоже управление поддеревом вне обычного потока, но через сохранение состояния, а не место в DOM
  • rc-21-lazy-suspense — Activity и Suspense дополняют друг друга: можно пред-рендерить скрытый сегмент, чьи данные грузятся через Suspense
Компонент Activity

0

1

Войти