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