React 19.3 が正式リリース

昨年experimentalとして公開された View TransitionsFragment Refs が、React 19.3でstableに昇格しました。加えて、コンポーネント単位でサーバーレンダリングをスキップできる browser() API、Server ComponentからContextを直接レンダリングする機能も入っています。

今回のリリースは「新しい構文」というより、既存アプリのUXを一段引き上げるための道具立てが中心です。アニメーション、ローディング状態、ハイドレーションといった、これまで場当たり的に処理してきた領域をReactが公式APIとして取り込んだ、という位置づけになります。

本記事では、実務でそのまま使える4つの変更点に絞って解説します。

根拠資料: React 19.3 公式リリースノート

Developer reviewing React 19.3 View Transitions API code on dual monitors Coding Session Visual

1. View Transitions — <ViewTransition> で囲むだけ

ブラウザの View Transition API を React コンポーネントとしてラップしたものです。enter / exit / update / share の4ケースを、Reactがツリーの変化から自動判定します。

import { ViewTransition, useState, startTransition } from 'react';
import { Video } from './Video';

export default function Component() {
  const [showItem, setShowItem] = useState(false);

  return (
    <>
      <button
        onClick={() => {
          // Transitionとしてマークしないとアニメーションが発火しません
          startTransition(() => {
            setShowItem((prev) => !prev);
          });
        }}
      >
        {showItem ? '➖' : '➕'}
      </button>

      {showItem && (
        <ViewTransition>
          <Video />
        </ViewTransition>
      )}
    </>
  );
}

注意点: startTransitionuseDeferredValueuse() 経由の更新でないとアニメーションは動きません。通常の setState は urgent update として扱われ、即時反映されます。

addTransitionType でアニメーションを分岐

同じ state 変化でも「次のスライド」と「前のスライド」では別のアニメーションが必要です。そこで addTransitionType を使います。

function nextSlide() {
  startTransition(() => {
    addTransitionType('next');   // CSS側で :active-view-transition-type(next) として指定可能
    setCurrentSlide((c) => c + 1);
  });
}

function previousSlide() {
  startTransition(() => {
    addTransitionType('previous');
    setCurrentSlide((c) => c - 1);
  });
}

Suspense fallback のアニメーション(実務で最も有用)

Suspense 境界を <ViewTransition> で囲むと、fallback から実コンテンツへの切り替えをアニメーションできます。ただし 原則を守らないとUXが悪化 します。

  • fallback はアニメーションなしで即時表示
  • fallback → 最終コンテンツの切り替えはアニメーション
  • suspend しない子はアニメーションなしで即時表示

これをコードに落とすと、update のみアニメーションを有効にする形になります。

<ViewTransition update="auto" enter="none" exit="none">
  <Suspense fallback={<VideoPlaceholder />}>
    <LazyVideo />
  </Suspense>
</ViewTransition>

キャッシュ済みのUIにまでアニメーションが付くと、逆にアプリが重く感じられます。enter / exit は無効化しておくのが無難です。

React ViewTransition component wrapping a video card with enter exit animation IT Technology Image

2. Fragment Refs — wrapper div なしで子DOMを制御

<div> で囲まずに、子ノードへイベント・フォーカス・IntersectionObserver を適用できるようになりました。ライブラリのコンポーネントが ref を forward していなくても動作します。

import { useRef, useEffect } from 'react';

function Component({ posts }) {
  const fragmentRef = useRef(null);

  useEffect(() => {
    const instance = fragmentRef.current;
    instance.focus(); // 最初の子要素にフォーカス
  }, []);

  return (
    <Fragment ref={fragmentRef}>
      {posts.map((post) => (
        <article key={post.id}>{post.title}</article>
      ))}
    </Fragment>
  );
}

FragmentInstance が提供するメソッドは以下の通りです。

  • addEventListener / removeEventListener / dispatchEvent — 第一階層の子要素のイベント管理
  • focus / focusLast / blur — depth-first のフォーカス移動
  • observeUsing / unobserveUsing — IntersectionObserver / ResizeObserver の接続
  • getClientRects / getRootNode / compareDocumentPosition / scrollIntoView — 計測とスクロール

3. use(browser()) — サーバーレンダリングのスキップ

localStorageIntl.DateTimeFormat().resolvedOptions().timeZone のように、ブラウザでのみ意味を持つ値を使うコンポーネントは、これまで useEffect + mounted state のような回避策が必要でした。19.3 からは公式APIが用意されています。

import { use } from 'react';
import { browser } from 'react-dom';

function TimeZone() {
  use(browser()); // サーバーでは suspend、クライアントではそのまま続行
  const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
  return <span>{timeZone}</span>;
}

条件付きでサーバーレンダリングを opt-out することも可能です。たとえば initialData がある場合はサーバーでもレンダリングし、ない場合はブラウザのみでレンダリングするパターンです。

function useBrowserQuery(query, options) {
  if (options.initialData === undefined) {
    use(browser()); // 初期データがない場合はブラウザのみで実行
  }
  return useQuery(query, options);
}

このパターンは SSR フレームワークで loader の値を渡す場面で特に有用です。初期データがあれば HTML に含まれて SEO に有利、なければクライアントで fetch する、という分岐が自然に書けます。

4. Server Component から Context を直接レンダリング

これまでは 'use client' モジュール内に Provider ラッパーを別途用意する必要がありました。19.3 では Context オブジェクトをそのまま import し、<UserContext value={...}> の形で直接レンダリングできます。

// user-context.js
'use client';
import { createContext } from 'react';
export const UserContext = createContext(null);

// server-component.js
import { UserContext } from './user-context';

export async function Layout({ children }) {
  const currentUser = await getCurrentUser();
  return (
    <UserContext value={currentUser}>
      {children}
    </UserContext>
  );
}

この技術の限界と注意点

  • View Transitions は DOM 専用 です。React Native 対応はまだ進行中で、RN で同じ挙動を期待してはいけません。
  • ブラウザ対応にばらつき があります。View Transition API 自体がすべてのブラウザで完全ではないため、フォールバック CSS を用意しておくのが安全です。
  • Suspense アニメーションの乱用は禁物。キャッシュ済みUIにまでアニメーションが付くと、体感パフォーマンスがむしろ落ちます。前述の3原則を必ず守ってください。
  • Trusted Types は CSP ポリシーと併用 して初めて意味を持ちます。require-trusted-types-for 'script' がなければ素通りします。

日本の開発現場での適用文脈

日本の受託開発やエンタープライズ案件では、まだ React 18 に留まっているケースも多く、19.3 の機能をすぐ導入できない環境もあるでしょう。ただし 自社サービスや新規プロジェクト では、browser() と View Transitions は今から検討する価値があります。特に管理画面ダッシュボードのように Suspense 境界が多い画面では効果が大きいです。

また、Qiita でも議論が活発な Next.js App Router 環境では、Server Component から Context を直接レンダリングできる変更のインパクトが大きく、これまで Provider ラッパーで生じていたボイラープレートをかなり削減できます。

次のステップ

  1. 公式ドキュメントの View Transitions ガイド を一読してください。
  2. addTransitionType と CSS の :active-view-transition-type() の組み合わせを実際に試してみましょう。
  3. Fragment Refs で既存のライブラリコンポーネントをラップするパターンを書いてみると、活用範囲が実感できます。

あわせて読みたい

Frontend engineer testing Fragment Refs and browser() API in React 19.3 on laptop

まとめ

React 19.3 は「派手な新構文」よりも、実務UXを磨くためのツールセットという性格が強いリリースです。View Transitions でアニメーションを React ツリーの変化に自然に乗せ、Fragment Refs で DOM 構造を変えずに振る舞いを注入し、browser() で SSR 回避パターンを整理する — この3つを押さえるだけでも、今回のリリースの半分は活用できたと言えます。

特に Suspense + View Transitions の組み合わせは、管理画面・ダッシュボード・EC の詳細ページのように「ローディング → コンテンツ」の切り替えが多い画面で即効性があります。ただしアニメーションは 少なく、正確に が正解です。

本コンテンツは、信頼性の高い情報源をもとにAIツールを活用して作成され、編集者によるレビューを経て公開されています。専門家によるアドバイスの代替となるものではありません。