概要
React 19.2でActivityが使えるようになりました。
UIの一部を非表示にしたり表示したりする機能です。
リリース当初のXを見ていたら「Vueのv-ifやv-showと同じでは?」といった意見をチラホラ見かけました1。
私はVueのことをあまり知らないので、本当に同じなのか、違いもあるのか気になり、調べてみました。
先に結論
結論としてv-ifとはだいぶ違います。
近いのはv-showですが、細かく言えばエフェクトのクリーンアップの扱いが異なります。
それぞれの関係性を整理すると以下の通りです。
- v-ifとの比較: 根本的に異なる(DOMの扱い、stateの保持、すべて違う)
- v-showとの比較: かなり近い(DOMと状態の扱いは同じ、エフェクト処理が異なる)
対象読者
- Reactの
Activityで何ができるのか知りたい方 - Vueの
v-ifやv-showとの違いを理解したい方
ReactのActivityの動作
まずActivityがどのように動作するのか確認します。
mode="hidden"時の動作
mode="hidden"の場合、以下のようになります。
-
display: noneでDOMを非表示にする - DOM要素は保持される
- stateは保持される
- エフェクトのクリーンアップが実行される
- 再表示時にエフェクトが再実行される
mode="visible"時の動作
mode="visible"の場合、通常通り表示されます。
- 保存されたstateが復元される
- エフェクトが再作成される
v-ifとの比較
DOMの扱い
React Activity(hidden時)
-
display: noneでDOMを非表示にする - DOM要素は保持される
Vue v-if(false時)
- DOM要素自体を削除する
- DOMツリーから完全に除去される
- 再表示時には再構築が必要
stateの保持
React Activity
- hidden時でもstateを保持する
- 再表示時に以前の状態が復元される
Vue v-if
- false時にコンポーネントが破棄される
- 内部stateも失われる
- 再表示時は初期状態から
エフェクト・ライフサイクル
React Activity(hidden時)
- エフェクトのクリーンアップが実行される
- サブスクリプション等がクリーンアップされる
- 再表示時にエフェクトが再実行される
Vue v-if(false時)
- コンポーネントが完全にアンマウントされる
-
onBeforeUnmountとonUnmountedが発火する - すべてのリソースが解放される
パフォーマンス特性
| 項目 | React Activity | Vue v-if |
|---|---|---|
| 初回レンダリング | hidden時もレンダー実行 | falseなら何もしない |
| 切り替えコスト | 低い(CSS変更とエフェクト再実行) | 高い(DOM再構築) |
| メモリ使用量 | 多い(DOM保持) | 少ない(削除) |
| 適用場面 | 頻繁な切り替え | 条件が滅多に変わらない |
結論: v-ifとは根本的に異なる
ActivityとV-ifは設計思想が異なります。
- Activity: 一時的な非表示(DOM保持、state保持)
- v-if: 条件付きで存在(DOM削除、state破棄)
v-showとの比較
DOMの扱い
React Activity
-
display: noneでDOMを非表示にする - DOM要素は保持される
Vue v-show
-
display: noneでDOMを非表示にする - DOM要素は保持される
この点は同じです。
stateの保持
React Activity
- hidden時でもstateを保持する
Vue v-show
- stateは常に保持される
この点も同じです。
エフェクト・ライフサイクル
React Activity(hidden時)
- エフェクトのクリーンアップが実行される
- サブスクリプション等がクリーンアップされる
- 再表示時にエフェクトが再実行される
Vue v-show
- コンポーネントは常にマウントされたまま
- ライフサイクルフックは発火しない
- エフェクトはアクティブなまま
ここが違います。
パフォーマンス特性
| 項目 | React Activity | Vue v-show |
|---|---|---|
| 初回レンダリング | hidden時もレンダー実行 | 常にレンダー実行 |
| 切り替えコスト | 低い(CSS変更とエフェクト再実行) | 非常に低い(CSS変更のみ) |
| メモリ使用量 | 多い(DOM保持) | 多い(DOM保持) |
| 適用場面 | 頻繁な切り替え | 頻繁な切り替え |
結論: v-showにかなり近いが、エフェクト処理が異なる
ActivityとV-showは動作が似ています。
最大の違いはエフェクトのクリーンアップです。
- Activity: hidden時にエフェクトをクリーンアップ
- v-show: エフェクトは常にアクティブ
Activityの独自機能
Activityには、v-ifやv-showにはない独自の機能があります。
バックグラウンドでのプリレンダリング
// まだ表示していないタブを事前にレンダリング
<Activity mode="hidden">
<ExpensiveComponent /> {/* 初回からhiddenでレンダー */}
</Activity>
初回レンダー時にmode="hidden"の場合、以下のようになります。
- ページ上では表示されないがレンダーは発生する
- 表示コンテンツより優先度は低い
- エフェクトのセットアップは起きない
- 事前にコードやデータをロードできる
選択的ハイドレーション(Selective Hydration)の最適化
function Page() {
const [activeTab, setActiveTab] = useState('home');
return (
<>
<TabButton onClick={() => setActiveTab('home')}>
Home
</TabButton>
<TabButton onClick={() => setActiveTab('video')}>
Video
</TabButton>
<Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
<Home />
</Activity>
<Activity mode={activeTab === 'video' ? 'visible' : 'hidden'}>
<Video />
</Activity>
</>
);
}
SSR時のハイドレーションを効率化します。
具体的には、表示部分から優先的にインタラクティブにします。
上記のコード例でいえば、非アクティブなタブに重い処理があっても、それが完了する前にアクティブなタブのボタンを操作することができます。
Vueでの代替手段
Vueで同等の機能が欲しい場合の選択肢を整理します。
v-show: DOMは保持、display切り替え
<!-- Activityに近いが、エフェクトは常にアクティブ -->
<Component v-show="visible" />
DOMと状態の扱いはActivityと同じです。
エフェクトのクリーンアップは行われません。
KeepAlive: コンポーネントインスタンス保持
<!-- コンポーネントをキャッシュして状態を保持 -->
<KeepAlive>
<Component v-if="visible" />
</KeepAlive>
v-ifと組み合わせることで、状態を保持しながらDOMをツリーから離脱させることができます。
非表示時はメモリ内にキャッシュされます。
onDeactivatedとonActivatedフックを使うことで、エフェクトの制御が可能です。
<script setup>
import { onActivated, onDeactivated } from 'vue';
// 非アクティブ化時の処理
onDeactivated(() => {
// タイマーやサブスクリプションの停止など
});
// アクティブ化時の処理
onActivated(() => {
// タイマーやサブスクリプションの再開など
});
</script>
3つの比較表
| 機能 | v-show | KeepAlive + v-if | React Activity |
|---|---|---|---|
| DOMの状態 | ツリー内に維持(display: none) | ツリーから離脱(メモリ保持) | ツリー内に維持(display: none相当) |
| state保持 | ○ | ○ | ○ |
| エフェクトクリーンアップ | ×(動き続ける) | △(onDeactivatedで制御) | ○(自動的にクリーンアップ) |
| プリレンダリング | × | × | ○(Concurrent機能) |
Activityの「hidden時にエフェクトを自動的にクリーンアップしつつDOMとstateを保持」という動作は、Vueでは完全には再現できません。
KeepAliveではonDeactivatedフックを使った手動制御が必要です。
具体的な使用例
React Activity: ビデオプレーヤーの状態保持
function App() {
const [activeTab, setActiveTab] = useState('home');
return (
<>
<TabButton onClick={() => setActiveTab('home')}>Home</TabButton>
<TabButton onClick={() => setActiveTab('video')}>Video</TabButton>
<Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
<Home />
</Activity>
<Activity mode={activeTab === 'video' ? 'visible' : 'hidden'}>
<VideoPlayer /> {/* 再生位置が保持される */}
</Activity>
</>
);
}
// VideoPlayerコンポーネント内でクリーンアップを追加
function VideoPlayer() {
const ref = useRef();
useLayoutEffect(() => {
const videoRef = ref.current;
return () => {
videoRef.pause(); // hidden時に再生停止
};
}, []);
return <video ref={ref} controls src="..." />;
}
Vue v-show: シンプルな表示切り替え
<template>
<button @click="show = !show">Toggle</button>
<Component v-show="show" />
</template>
<script setup>
import { ref } from 'vue';
const show = ref(true);
</script>
Vue v-if: 完全なリソース解放
<template>
<button @click="show = !show">Toggle</button>
<HeavyComponent v-if="show" />
</template>
<script setup>
import { ref } from 'vue';
const show = ref(true);
</script>
まとめ
ReactのActivityは、Vueのv-ifやv-showと比較されることがありますが、それぞれ異なる特徴を持っています。
v-ifとは根本的に異なります。
v-showとはかなり近いですが、エフェクトのクリーンアップの扱いが異なります。
比較のまとめ
| 観点 | React Activity | Vue v-show | Vue v-if |
|---|---|---|---|
| DOMの扱い | 保持(display: none) | 保持(display: none) | 削除・再構築 |
| state保持 | ○ | ○ | × |
| エフェクトクリーンアップ | ○ | × | ○ |
| 切り替えコスト | 低い | とても低い | 高い |
| 主な用途 | 頻繁な切り替え、プリレンダリング | 頻繁な切り替え | 条件分岐 |
ReactのActivityは「hidden時にエフェクトをクリーンアップしつつDOMとstateを保持」という独特な動作で、Vueのv-ifやv-showとは異なる方針の機能を提供しています。
-
全然違うものでは?と思った方もいらっしゃるかもしれませんが、実際にそういう意見があったんです。 ↩