View Transitions と React Router の統合

React Router 6.4 の組み込み unstable_useViewTransitionState フックでルート切替をアニメーション。古いバージョン向けの flushSync による手動連携も解説。

Integration

詳細な説明

React Router 6.4+ ビルトインサポート

React Router 6.4(2024年3月)以降、すべての <Link>viewTransition プロパティを受け付けます:

import { Link } from 'react-router-dom';

<Link to="/products/42" viewTransition>
  Product 42
</Link>

ユーザーがクリックすると、React Router が内部でナビゲーションを document.startViewTransition() でラップします。追加設定は不要。

アクティブなトランジションごとの条件付きスタイリング

トランジション中のルートを別スタイルにするには useViewTransitionState フックを使います:

import { useViewTransitionState } from 'react-router-dom';

function ProductCard({ id }) {
  const isTransitioning = useViewTransitionState(`/products/${id}`);
  return (
    <div style={{ viewTransitionName: isTransitioning ? `product-${id}` : 'none' }}>
      …
    </div>
  );
}

カードが現在のトランジションのソースである 場合のみ 動的にオプトインされ、グリッド全体に一意の名前を付ける爆発を回避します。

フックなしの手動統合(古い React Router)

import { flushSync } from 'react-dom';
import { useNavigate } from 'react-router-dom';

function MyLink({ to, children }) {
  const navigate = useNavigate();
  return (
    <a onClick={(e) => {
      e.preventDefault();
      if (!document.startViewTransition) return navigate(to);
      document.startViewTransition(() => {
        flushSync(() => navigate(to));
      });
    }}>{children}</a>
  );
}

flushSync は React にナビゲーションを 同期的に コミットさせ、ブラウザが2枚目のスナップショットを即座に取れるようにします。これがないと React がアップデートをバッチ処理し、スナップショットが早すぎるタイミングで取られます。

Suspense バウンダリ

ルートが <Suspense> を使う場合、トランジションは Suspense バウンダリの解決を待ってからスナップショットを取ります。Suspense 内に意味のあるスケルトンを描画し、コンテンツとスピナーではなく2つのリアルなビュー間でクロスフェードさせてください。

ユースケース

React Router 6.4+ でナビゲーションする本番 React アプリ。同じアイテムが異なるレイアウトで現れる商品詳細ページ、写真ギャラリーのディープリンク、「リスト→詳細→リスト」フロー全般で特に強力。

試してみるView Transitions API ジェネレーター

フルツールを開く