React 19.2で追加されたActivityコンポーネントを試してみる
この記事はReact Tokyoアドベントカレンダーの9日目の記事です。
React 19.2にて追加されたActivityについて、どういったものなのか、どんなシーンで利用することができるのか紹介していきます。
Activityコンポーネントとは
UIの一部を表示・非表示を切り替えながらStateやDOMの状態を維持し、パフォーマンスを最適化する仕組み。
使い方
1. stateの状態を担保したまま表示の切り替えを行う
<Activity>コンポーネントは、条件付きレンダリング({isVisible && <Component />})と異なり、非表示状態になっても子のコンポーネントを完全にアンマウントせず、内部のState(状態)を保持します。
これにより、ユーザーが一時的にUI要素を隠した後で再度表示した際に、直前の操作で変更された状態(例:アコーディオンの開閉状態、チェックボックスの状態など)がそのまま復元されます。
📝 使用例:サイドバーの状態保持
例えば、開閉可能なサイドバーがあり、その中のセクションの開閉状態をStateで管理している場合を考えます。
// isShowingSidebar が true/false でサイドバーの表示を切り替える
<Activity mode={isShowingSidebar ? 'visible' : 'hidden'}>
<Sidebar /> {/* Sidebarの内部State(開閉状態など)が保持される */}
</Activity>
2. 入力した情報を担保したまま表示の切り替えを行う
上記のState保持の応用として、フォーム要素やテキストエリアなど、入力した情報(DOMの状態)を維持したい場合にも<Activity>が有効です。
従来のアンマウント方式では、コンポーネントが破棄されると、入力フィールドの値もDOMから消えてしまいます。しかし、<Activity mode="hidden">ではDOM要素自体はdisplay: noneで残るため、入力フィールドの一時的な状態(下書きメッセージなど)が保持されます。
📝 使用例:タブ切り替え時の入力フィールド保持
お問い合わせフォームとホーム画面をタブで切り替える際に、コンタクトタブにメッセージを入力した状態でホームタブに切り替えても、入力内容が消えません。
// activeTab が 'contact' の時のみ表示
<Activity mode={activeTab === 'contact' ? 'visible' : 'hidden'}>
<ContactForm /> {/* <textarea>などの入力情報が保持される */}
</Activity>
// activeTab が 'home' の時のみ表示
<Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
<HomeContent />
</Activity>
3. ページの読み込みを高速化する
<Activity>をコンテンツのプリレンダー(事前準備)に活用することで、ユーザーがコンテンツを初めて見る際の待ち時間を短縮し、ページの読み込みや遷移を高速化できます。
具体的には、ユーザーが次にアクセスする可能性の高いコンテンツを、最初にページをロードした時点でmode="hidden"にして配置しておきます。
-
低い優先度でのレンダリング:
hiddenモードの子コンポーネントは、表示されているコンテンツよりも低い優先度でレンダリングされます。 -
データフェッチ:
<Suspense>と組み合わせることで、非表示の間に必要なデータ(APIデータなど)をバックグラウンドでフェッチできます。 - エフェクトの停止: エフェクトは実行されないため、バックグラウンドでの不要な副作用(ログ、外部システムとの接続など)の発生を防ぎます。
これにより、ユーザーが実際にそのコンテンツを表示する操作を行ったときには、すでにコードとデータが準備されている状態(Hydrationの高速化)となり、待機時間が短縮されます。
📝 使用例:初回読み込み時のデータプリロード
タブAがアクティブでも、タブBのデータを裏側で読み込んでおく例です。
<Activity mode={activeTab === 'tabA' ? 'visible' : 'hidden'}>
<TabAContent />
</Activity>
{/* 初回ロード時から hidden にして、TabBで使用するデータやコードを事前ロード */}
<Activity mode={activeTab === 'tabB' ? 'visible' : 'hidden'}>
<TabBContentWithSuspense />
</Activity>
Activityの利用場面について
<Activity>はとても強力ですが、「どんな場面でも使うべき」というわけではありません。以下の基準を知っておくと、不要なパフォーマンスコストを避けつつ最大限のメリットを得られます。
利用した方がよい場面
- UIを非表示にしても State を保持したいとき (フォーム入力、アコーディオン、スクロール位置など)
- ユーザー体験を損なわずに表示切り替えしたいとき (タブ、モーダル、サイドバーなどの状態を保持したい場合)
- 事前レンダリングでページ遷移を高速化したいとき (次に開くタブやページを裏側で準備しておきたい場合)
- コンポーネントをアンマウントさせたくないとき (重い初期化処理やデータフェッチを毎回実行させたくない)
利用しない方がよい版
- DOMが大量に残り続けることでメモリ圧迫のリスクがある場合
- 常に表示/非表示を切り替えるわけではない静的なUI
- SSR(サーバー側)で hidden コンテンツを大量にプリレンダーしたくない場合
注意点と制約事項
パフォーマンスへの影響
<Activity mode="hidden">を使用すると、非表示のコンポーネントもDOMツリー上に残り続けるため、多数のコンポーネントを同時にhidden状態で保持すると、メモリ使用量が増加する可能性があります。必要なコンポーネントのみを保持し、長期間使用されないコンポーネントは完全にアンマウントすることを検討してください。
CSSとの相互作用
mode="hidden"の場合、内部的にはdisplay: noneが適用されますが、独自のCSSで表示制御を行っている場合は競合する可能性があります。<Activity>による表示制御を優先する場合は、CSSの詳細度に注意が必要です。
エフェクトの実行タイミング
hiddenモードではuseEffectなどのエフェクトは実行されません。これにより不要な副作用は防げますが、コンポーネントがvisibleに切り替わった際にエフェクトが初めて実行されることに注意してください。データの初期化やイベントリスナーの登録タイミングを考慮する必要があります。
アクセシビリティの考慮
mode="hidden"で非表示にされたコンテンツは、スクリーンリーダーからもアクセスできません。適切なaria-*属性の使用や、キーボードナビゲーションへの配慮を忘れないようにしましょう。
最後に
React 19.2から追加された<Activity>コンポーネントを活用することで、状態を保持したままの表示切り替えやコンテンツのプリロードが簡潔に実装できるようになりました。ぜひ実際のプロジェクトで試してみてください。
Discussion