logo

React19.2で導入されたActivityの紹介

2025-11-14
2 months ago

開発環境

  • react 19.2.0
  • react-dom 19.2.0

前提

React 19.2 で新たに導入されたコンポーネント Activity は、DOMツリーから完全にアンマウントせずに「視覚的には非表示/再表示できる子コンポーネント領域」を作るためのラッパーです。

公式ドキュメントでは「UI と内部状態(state)を子要素に保持しつつ、表示・非表示を制御できる」ものと説明されています。

従来、条件付きレンダリング(例: isVisible && <Sidebar /> )を使うと、表示を切り替えるたびにコンポーネントがアンマウント/マウントされてしまい、内部 state や DOM の状態が失われてしまうケースがありました。

<Activity> を用いることで、アンマウントせずに「隠す」「表示する」が可能になります。

本題

Activityの基本仕様

(1)<Activity mode={visibility}> のように用い、mode プロパティには "visible" または "hidden" を指定します(デフォルトは "visible"

(2)mode="hidden" のとき

  👉 子要素は display: none によって視覚的に非表示になります

  👉 Effects(副作用)をクリーンアップ(アンマウント相当)し、不要な更新・サブスクリプションを避けられます

  👉 ただし、子要素はアンマウントされず「状態(state)」「DOM構造」は保持されます

(3)mode="visible" のとき

  👉 通常どおり表示と更新が行われます

主な活用シーン

(1)状態を保持したまま UI を切り替えたい場合 例:サイドバー、タブ、入力フォームなどで「一旦隠して、再表示したときに以前の入力値・展開状況を保持したい」場面。条件付き表示では state が破棄されるが、<Activity> では保持できます。

import { Activity, useState } from 'react';

function Sidebar() {
  const [expanded, setExpanded] = useState(false);

  return (
    <div className="h-full w-56 border-r border-gray-200 bg-gray-100 p-4">
      <button
        className="rounded bg-blue-600 px-3 py-2 text-sm text-white hover:bg-blue-700"
        onClick={() => setExpanded(!expanded)}
      >
        {expanded ? '閉じる' : '開く'}
      </button>

      {expanded && (
        <ul className="mt-3 space-y-2 rounded border border-blue-100 bg-blue-50 p-3">
          <li>Item 1</li>
          <li>Item 2</li>
          <li>Item 3</li>
        </ul>
      )}
    </div>
  );
}

function App() {
  const [showSidebar, setShowSidebar] = useState(true);

  return (
    <div className="flex h-screen">
      {/* Activity でサイドバー表示/非表示 */}
      <Activity mode={showSidebar ? 'visible' : 'hidden'}>
        <Sidebar />
      </Activity>

      <main className="p-6">
        <button
          className="mb-4 rounded bg-gray-700 px-3 py-2 text-sm text-white hover:bg-gray-800"
          onClick={() => setShowSidebar((v) => !v)}
        >
          サイドバーの{showSidebar ? '非表示' : '表示'}
        </button>

        <h1 className="text-2xl font-bold">Main Content</h1>
      </main>
    </div>
  );
}

export default App;

この例では、サイドバーを非表示にしても Sidebar の内部 expanded 状態は保持され、再表示時も前回の状態が復元されます。


(2)先読み・プリレンダリング用途

例:将来的に表示される可能性のあるコンポーネントを “hidden” モードで先にレンダリングしておき、ユーザーが切り替えた際に表示を高速化します。

import { Activity } from 'react';

function HeavyComponent() {
  // 大量の初期処理/データ取得など
  return <div>重たい画面</div>;
}

export default function App() {
  const [showHeavy, setShowHeavy] = useState(false);

  return (
    <>
      <button onClick={() => setShowHeavy(true)}>次の画面へ</button>

      <Activity mode={showHeavy ? 'visible' : 'hidden'}>
        <HeavyComponent />
      </Activity>
    </>
  );
}

ここでは HeavyComponent が非表示中でも低優先度でレンダリング準備されており、切り替え後の応答性が向上します。

注意事項

  • Activity 内の子が「テキストだけ(DOM 要素なし)」の場合、DOM 要素を隠す対象がなく、期待どおりに動作しない可能性があります。公式ドキュメント上で注意喚起があります。
  • <video>, <audio>, <iframe> など、明示的にクリーンアップが必要な副作用を含む要素を隠す場合は、クリーンアップを確実に実装する必要があります。
  • 非表示中(mode="hidden")では、useEffect 内の処理が実行されない点を理解しておく必要があります。例えば非表示中にデータ取得を実行したい場合、Suspense 対応のフレームワークと組み合わせる必要があります。

さいごに

<Activity> は、React アプリケーションにおいて「表示・非表示を切り替えたいが、状態を捨てたくない」パターンをより簡潔に、かつ効率的に扱える新しい道具です。特にタブ切り替えやモーダル、サイドバーの表示制御などで有効です。 また、次画面の先読みなどパフォーマンス最適化の観点でも活用できます。ただし、動作の前提や副作用の扱いについては注意が必要です。これを機に、React 19.2 の Activity をぜひ導入検討してみてください。

参照