<Activity>:UIの表示制御におけるUXとパフォーマンスの改善
<Activity>は、React 19.2で導入された新しいコンポーネントで、従来の条件付きレンダリングやCSSのdisplayプロパティを使用した表示制御の課題を解決します。
<Activity>のAPIは非常にシンプルで、表示・非表示に合わせて、modeプロパティを"visible"、"hidden"のいずれかに設定するだけです。これで子コンポーネントの表示状態が制御できます。modeプロパティが、boolean値ではなく"visible"または"hidden"の文字列を取るという仕様に、今後の拡張性が感じられます。
<Activity mode={isVisible ? "visible" : "hidden"}>
<MyComponent />
</Activity>
<Activity>の振る舞い
では<Activity>がmodeプロパティに応じてどのように振る舞うのかを見ていきましょう。以下、useEffectで実行される副作用処理をEffectと呼びます。
-
mode="visible"のとき: 通常通りレンダリングし、UI を表示してEffectを実行 -
mode="hidden"のとき: UIはdisplay:none !importantで非表示にしつつ、State/DOMは維持し、Effectはクリーンアップ
この振る舞いが条件付きレンダリングや単なるdisplay: noneとは異なる挙動を生み出します。
従来の方法と<Activity>の挙動の違い
実際にサンプルコードを使って、CSSのdisplayプロパティを使用した表示制御、従来の条件付きレンダリング、そして<Activity>を使った表示制御の違いを見てみましょう。
まず、リスト2の通り入力コンポーネントを用意します。
function MyForm({ formId }) {
const [name, setName] = useState("");
useEffect(() => {
console.log(`mounted: ${formId}`);
return () => {
console.log(`cleanup: ${formId}`);
};
}, []);
return (
<div style={{ border: "1px solid silver", margin: "0.5em", padding: "0.5em" }}>
<label>
名前: <input value={name} onChange={(e) => setName(e.target.value)} />
</label>
</div>
);
}
次にリスト3は、3つの方法でフォームコンポーネントを表示・非表示に切り替える例です。
// (1) 条件付き表示制御 (display プロパティ)
<div style={{ display: isVisible ? "block" : "none" }}>
<MyForm formId="1. 条件付き表示制御" />
</div>
// (2) 条件付きレンダリング
{isVisible && <MyForm formId="2. 条件付きレンダリング" />}
// (3) Activity
<Activity mode={isVisible ? "visible" : "hidden"}>
<MyForm formId="3. Activity" />
</Activity>
図2はサンプルコード01を実行した画面例です。ロードされた時点で、Consoleログに "mounted: 1. 条件付き表示制御" と表示されており、すでに(1)のEffectが実行されていることがわかります。
一方、(2)と(3)ではまだEffectは実行されていません。
次に「表示」ボタンをクリックしてフォームを表示させると、図3のように(2)と(3)のEffectが実行され、Consoleログに "mounted: ~" が表示されます。
ここで「非表示」ボタンをクリックしてフォームを再度非表示にすると、図4のように(2)と(3)は副作用のクリーンアップ処理が実行(Consoleログに "cleanup" が表示)されます。
ただし、(2)ではDOMから完全に消えてしまっているのに対し、<Activity>を使った(3)は非表示になるだけでDOMに要素が残っていることがポイントです。
さらに図5のように「表示」状態で、各テキストボックスに文字列を入力してから「非表示」ボタンをクリックしてみましょう。
(2)の条件付きレンダリングではコンポーネントがアンマウントされるため、state(=入力した文字列)は失われます。一方、<Activity>を使った(3)は非表示になるものの、stateやDOMが維持されており、再度「表示」ボタンをクリックすると、入力した文字列が保持されていることがわかります。
<Activity>のメリットとユースケース
ここまで見てきたように、<Activity>の特徴は「非表示時にアンマウントせず、stateやDOMを保持して、Effectのみクリーンアップする」点にあります。非表示のときに無駄な処理を抑制しつつ、再表示時に状態を復元できることで、UXの向上やパフォーマンス改善が期待できます。
そのほかにも以下のメリットがあります。
- プリレンダリング(先読み):hiddenモードであっても子コンポーネントをレンダリングできる(Effectは実行されない) → データ取得、コード読み込み、画像ロードを先に進めておくことで、表示切り替え時の遅延を軽減できる。
- 優先度制御:hidden領域のレンダリング更新は優先度を下げて実行されるよう制御されるため、ユーザーに見えているUIに対する応答性を優先できる。
- 切り替え時のアニメーション制御:ActivityはViewTransitionと併用したトランジション動作をサポートしており、表示/非表示の切り替え時にenter/exitアニメーションを発生させる制御も可能。
いずれもパフォーマンスやUXの向上に寄与する重要なポイントです。ただしプリレンダリングについては、Suspenseとの組み合わせが必要になり、useEffect内のデータ取得は先読みでは実行されない点に注意が必要です。
<Activity>はタブUIやサイドバーなど表示・非表示を切り替えることが前提のコンポーネントで特に有効です。状態保持が求められるフォームや設定画面、ダッシュボードなどでの利用が考えられます。
すべてを<Activity>に置き換える必要はなく、従来の手法を使っている部分で<Activity>に置き換えられそうな場所から段階的に導入していくのがよいでしょう。
