Сервис развивается: тестируем формат, собираем идеи, улучшаем сервис. Есть идеи? Написать
Дайджесты новостей
Стилизованная схема: карточка каталога плавно превращается в крупную карточку детали, а группа соседних карточек измеряется без оберточного контейнера.

React 19.3: стабилизация компонента ViewTransition и поддержка ссылок на фрагменты

Официальный релиз библиотеки React 19.3, опубликованный в реестре npm, перевел в стабильный статус сразу две долгожданные возможности архитектуры: компонент <ViewTransition> и поддержку ссылок (ref) для фрагментов. Обе концепции находились на стадии экспериментов в Canary-каналах около года, а теперь стали частью общедоступного API для веб-приложений.

Нововведения решают две давние боли фронтенд-инженеров: создание плавных морфинг-анимаций между экранами без тяжелых сторонних анимационных библиотек и чистое управление группами DOM-узлов без добавления лишних тегов-оберток.

Декларативные анимации переходов: компонент <ViewTransition>

До появления компонента <ViewTransition> интеграция браузерного View Transition API в React требовала громоздких императивных вызовов document.startViewTransition() внутри хуков useEffect или хаков с ручным управлением жизненным циклом компонентов. Это часто приводило к рассинхронизации виртуального DOM и реальных экранных снимков браузера.

Компонент <ViewTransition> предоставляет полностью декларативную модель. Он связывает нативные анимации переходов браузера с механизмом конкурентного рендеринга React.

Ключевое архитектурное правило: компонент <ViewTransition> активирует анимации только тогда, когда изменение состояния обернуто в переход — через startTransition, хук useDeferredValue или компонент <Suspense>. Срочные синхронные обновления намеренно игнорируют анимацию, чтобы интерфейс не терял отзывчивости при вводе текста или быстрых кликах.

В жизненном цикле перехода компонент выделяет четыре фазы:

  • enter — появление нового элемента в DOM-дереве;
  • exit — удаление элемента из структуры;
  • update — изменение геометрических размеров или позиции узла;
  • share — разделяемый переход между двумя разными компонентами на разных экранах (например, превращение превью карточки товара в полноэкранный заголовок).

Ниже представлен первый пример реализации: базовое переключение табов каталога с плавной анимацией через <ViewTransition>:

import React, { useState, startTransition } from 'react';
import { ViewTransition } from 'react';

export function TabSelector({ categories }) {
  const [activeTab, setActiveTab] = useState(categories[0]);

  const handleSelect = (category) => {
    // Обязательное условие: оборачиваем обновление в transition
    startTransition(() => {
      setActiveTab(category);
    });
  };

  return (
    <div className="tab-container">
      {categories.map((cat) => (
        <button key={cat.id} onClick={() => handleSelect(cat)}>
          {cat.name}
          {activeTab.id === cat.id && (
            // Декларативный индикатор активного таба плавно перемещается
            <ViewTransition name="tab-indicator">
              <span className="active-pill" />
            </ViewTransition>
          )}
        </button>
      ))}
    </div>
  );
}

Ссылки на фрагменты: избавление от оберточных div

Вторым важным новшеством React 19.3 стала нативная поддержка ref на фрагментах <React.Fragment> (и в сокращенном синтаксисе <>...</>).

Исторически фрагменты использовались исключительно для группировки соседних JSX-элементов без создания промежуточного DOM-узла. Однако если родительскому компоненту требовалось измерить суммарные границы группы элементов (например, для позиционирования тултипа или виртуализации списка), разработчикам приходилось оборачивать группу в настоящий <div>. Это нарушало сетки CSS Grid, ломало flexbox-контейнеры и загрязняло верстку.

В React 19.3 ссылка, переданная во фрагмент, предоставляет доступ к коллекции реальных дочерних DOM-узлов или к их объединенному ограничивающему прямоугольнику (Bounding Box).

Развивая наш сценарий, второй блок кода показывает, как объединить анимированный переход <ViewTransition> с измерением группы элементов через Fragment Ref без единого лишнего узла разметки:

import React, { useRef, useLayoutEffect, useState, startTransition } from 'react';
import { ViewTransition } from 'react';

export function AnimatedProductGrid({ items }) {
  const [selectedGroup, setSelectedGroup] = useState('all');
  const fragmentRef = useRef(null);

  useLayoutEffect(() => {
    if (fragmentRef.current) {
      // Получаем доступ к границам группы узлов фрагмента напрямую
      const domNodes = fragmentRef.current.getNodes?.() || [];
      console.log(`Отрендерено элементов во фрагменте: ${domNodes.length}`);
    }
  }, [selectedGroup]);

  return (
    <div className="catalog-grid">
      <button onClick={() => startTransition(() => setSelectedGroup('featured'))}>
        Показать избранное
      </button>

      {/* Фрагмент принимает ref и не вносит лишний div в CSS Grid */}
      <React.Fragment ref={fragmentRef}>
        {items.map((item) => (
          <ViewTransition key={item.id} name={`product-card-${item.id}`}>
            <article className="product-card">
              <h3>{item.title}</h3>
              <p>{item.price}</p>
            </article>
          </ViewTransition>
        ))}
      </React.Fragment>
    </div>
  );
}
Ограничения совместимости

Поддержка компонента <ViewTransition> в релизе React 19.3 стабилизирована исключительно для веб-платформы (React DOM). В мобильном рантайме React Native нативная поддержка переходов экранов находится на этапе проектирования спецификации и пока недоступна в стабильных сборках.

Стабилизация <ViewTransition> и ссылок на фрагменты в React 19.3 делает декларативную работу с браузерными визуальными стандартами естественной частью экосистемы фреймворка, сокращая объем вспомогательного JavaScript-кода и очищая DOM-структуру приложений.