20251031
2025/10/31
React
<Activity>
React 19.2でリリースされた Activity コンポーネント。コンポーネントツリー上の Activity バウンダリとして機能する。
Activity バウンダリ配下のコンポーネントは表示/非表示およびレンダリングの優先度のコントロールが可能になる。
<Activity mode={isActive ? 'visible' : 'hidden'}>
<ChildComponent>
</Activity>mode propでが 'visible' の場合は表示され、 'hidden' の場合は非表示となる。非表示となった子孫コンポーネントは引き続きレンダリングされるものの、レンダリングの優先度が下がる。
表示/非表示の切り替えは単純に display: none !import の付与で行われている模様。
CSSで表示を切り替えているだけなので、非表示の状態でも子孫コンポーネントが持つステートはそのまま保持される。
ただし、非表示状態の子孫コンポーネントはuseEffectを処理しない。非表示になると同時にuseEffectはクリーンアップされる。
function Inner() {
useEffect(() => {
console.log('do effect');
setTimeout(() => {
console.log('hello');
}, 1000);
return () => {
console.log('cleanup effect');
}
}, []);
return <div>aaa</div>;
}
function App() {
const [showCounter, setShowCounter] = useState(false);
return (
<>
<Activity mode={showCounter ? 'visible' : 'hidden'}>
<Inner />
</Activity>
<button onClick={() => setShowCounter((showCounter) => !showCounter)}>
toggle counter
</button>
</>
)
}上記の場合、コンポーネントが非表示から表示に変わるたびに Inner の useEffect が処理される。
「UIを一時的に非表示にするが、その間は副作用を止めておきたい」場合に便利。
とはいえ use を使ったフェッチなどは動作する。あくまで useEffect を無効にする。
// useにはキャッシュ化にあるPromiseを使う必要がある
const cache = new Map<string, Promise<string>>();
// フェッチ→テキスト取得まで行うPromiseを返す
function fetchUrl(url: string): Promise<string> {
let p = cache.get(url);
if (!p) {
p = fetch(url).then(res => res.text());
cache.set(url, p);
}
return p;
}
function Inner() {
// Activityが非表示状態でもフェッチする
const data = use(fetchUrl('<https://dummyjson.com/test>'));
return <><textarea defaultValue={JSON.stringify(data)} /></>;
}
function App() {
const [showCounter, setShowCounter] = useState(false);
return (
<>
<Activity mode={showCounter ? 'visible' : 'hidden'}>
<Inner />
</Activity>
<button onClick={() => setShowCounter((showCounter) => !showCounter)}>
toggle counter
</button>
</>
)
}まとめるとActivityでは
- コンポーネントの表示/非表示を切り替えられる
- 非表示になったコンポーネントはレンダリングの優先度が下がる
- 非表示になったコンポーネントはuseEffectクリーンアップされる(表示時に再度実行される)
今後visibleとhidden以外のモードも追加する予定、とのこと。