React 19.2
公開日: 2025-10-01
作成者: The React Team
React 19.2 が npm で利用可能になりました!これは昨年に出た 3 回目のリリースで、前回は 19(12月)と 19.1(6月)です。本記事では React 19.2 の新機能の概要と注目すべき変更点を紹介します。
新しい React 機能
-
<Activity />
- useEffectEvent
- cacheSignal
- Performance Tracks
<Activity />
<Activity> を使うとアプリを「アクティビティ」に分割し、制御や優先度付けができます。これはアプリの一部を条件付きレンダリングする代替として使えます。
例:
{ isVisible && <Page /> }
<Activity mode={ isVisible ? 'visible' : 'hidden' }>
<Page />
</Activity>
React 19.2 では Activity は visible と hidden の2つのモードをサポートします。
hidden: children を隠し、effects をアンマウントし、React にやることが残っていない間はすべての更新を延期します。
visible: children を表示し、effects をマウントし、更新を通常通り処理します。
これにより、画面に表示されているもののパフォーマンスに影響を与えずに、アプリの隠れた部分を事前レンダリングしてレンダリングを続けることが可能になります。ユーザーが次に移動しそうな隠れた部分を事前読み込みしたり、離れた部分の状態を保存しておくことで、データ・CSS・画像の事前読み込みや、戻るナビゲーション時の入力フィールドなどの状態保持が容易になります。
将来的には異なるユースケース向けにさらに多くのモードを追加する予定です。使用例は Activity docs を参照してください。
useEffectEvent
useEffect の一般的なパターンとして、外部システムからの「イベント」をアプリ側に通知することがあります。例えばチャットルームが接続したときに通知を表示するようなケースです。
問題: Effect の中で使う値(例: theme)が変わると Effect 全体が再実行されてしまい、意図しない再接続などが起こることがあります。多くの開発者は lint ルールを無効にして依存配列から除外しますが、それは後の変更でバグを招く可能性があります。
useEffectEvent を使うと、イベント部分のロジックを Effect 本体から切り離せます:
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => { onConnected(); });
connection.connect();
return () => connection.disconnect();
}, [roomId]);
}
Effect Events は DOM イベントと同様に常に最新の props と state を参照します。Effect Events は依存配列に宣言すべきではありません。linter がそれらを依存関係として挿入しようとしないよう、eslint-plugin-react-hooks@latest にアップグレードする必要があります。
注意: Effect Events はその Effect と同じコンポーネントまたは Hook 内でしか宣言できません。これらの制約は linter によって検証されます。
いつ useEffectEvent を使うか
Effect から発火される「イベント」的な関数に対して useEffectEvent を使うべきです(これが "Effect Event" と呼ばれる理由です)。ただし、lint エラーを黙らせるためだけに何でも useEffectEvent で包む必要はなく、むしろそれでバグを招くことがあります。イベントとエフェクトを分離する考え方の詳細は: Separating Events from Effects を参照してください。
cacheSignal (React Server Components 向け)
cacheSignal は React Server Components 用です。cacheSignal を使うと cache() の寿命が終わったタイミングを知ることができます:
import { cache, cacheSignal } from 'react';
const dedupedFetch = cache(fetch);
async function Component() {
await dedupedFetch(url, { signal: cacheSignal() });
}
これにより、キャッシュ内の結果がもはや使われなくなったときに作業をクリーンアップしたり中断したりできます。例えば:
- React がレンダリングを正常に完了したとき
- レンダリングが中断されたとき
- レンダリングが失敗したとき
詳細は cacheSignal docs を参照してください。
Performance Tracks
React 19.2 は Chrome DevTools のパフォーマンスプロファイルにカスタムトラックを追加し、React アプリのパフォーマンスに関する情報をより詳しく提供します。詳細は React Performance Tracks docs を参照してください。ここではハイレベルの概要を示します。
-
Scheduler ⚛
- Scheduler トラックは、ユーザー操作に対する "blocking" や startTransition 内の更新の "transition" など、異なる優先度で React が何に取り組んでいるかを示します。
- 各トラック内で、どのような作業が行われているか(更新をスケジュールしたイベント、レンダリングがいつ行われたかなど)が表示されます。
- 優先度によって更新がブロックされているときや、React が続行の前にペイントを待っているときの情報も表示されます。
-
Components ⚛
- Components トラックは、React がレンダリングや effect の実行のために処理しているコンポーネントツリーを表示します。
- "Mount"(children のマウントや effects のマウント)や、レンダリングが React 以外の作業に譲ってブロックされているときの "Blocked" などのラベルが表示されます。
- コンポーネントのレンダリングや effect 実行のタイミングと所要時間を理解するのに役立ちます。
新しい React DOM 機能
Partial Pre-rendering
19.2 では、アプリの一部を事前にレンダリングし、後で再開(resume)して埋める機能を追加しました。これを "Partial Pre-rendering" と呼びます。静的な部分を CDN から配信し、後からシェルを再開して動的コンテンツを埋める用途に使えます。
事前レンダリングして後で再開するには、まず AbortController とともに prerender を呼び出します:
const { prelude, postponed } = await prerender(<App />, {
signal: controller.signal,
});
await savePostponedState(postponed);
その後、prelude をクライアントに返し、後で resume を呼んで SSR ストリームに再開します:
const postponed = await getPostponedState(request)
const resumeStream = await resume(<App />, postponed)
// ストリームをクライアントへ送る
あるいは resumeAndPrerender を呼んで SSG 用の静的 HTML を取得できます:
const postponedState = await getPostponedState(request);
const { prelude } = await resumeAndPrerender(<App />, postponedState);
詳細は各 API のドキュメントを参照してください:
- react-dom/server:
resume(Web Streams 用)、resumeToPipeableStream(Node Streams 用)
- react-dom/static:
resumeAndPrerender(Web Streams 用)、resumeAndPrerenderToNodeStream(Node Streams 用)
さらに、prerender API は resume API に渡す postponed state を返すようになりました。
注目すべき変更点
Batching Suspense Boundaries for SSR
これまで Suspense 境界はクライアントでレンダリングされた場合とサーバー側ストリーミングで出力された場合で表示のされ方が異なる振る舞いをするバグがありました。19.2 では、サーバーレンダリングの Suspense 境界の reveal(落ちていたフォールバックから実際のコンテンツへの切り替え)を短時間バッチ処理するようにして、より多くのコンテンツをまとめて公開できるようにしました。これによりクライアントレンダリング時の振る舞いと整合させます。
以前はストリーミング SSR 時にサスペンスのコンテンツがすぐにフォールバックを置き換えていましたが、19.2 では短い時間だけバッチすることで、より多くのコンテンツをまとめて reveal できるようにします。この修正は SSR 中の Suspense 向けの <ViewTransition> サポートに備えるものでもあります。
注意: React はコア Web Vitals や検索ランキングに影響しないようヒューリスティックを用いてスロットリングを制御します。例えば合計ページロード時間が 2.5 秒(LCP の「良い」目安)に近づいている場合、React はバッチ処理を中止して即座にコンテンツを公開します。
SSR: Web Streams の Node サポート
React 19.2 は Node.js でのストリーミング SSR に対する Web Streams のサポートを追加しました:
renderToReadableStream が Node.js で利用可能に
prerender が Node.js で利用可能に
- 新しい resume API(
resume / resumeAndPrerender)が Node.js で利用可能に
注意点 — Node.js では Node Streams を優先する
Node.js 環境では引き続き Node Streams API を使うことを強く推奨します:
renderToPipeableStream
resumeToPipeableStream
prerenderToNodeStream
resumeAndPrerenderToNodeStream
理由: Node 上では Node Streams の方が Web Streams より高速で、さらに Web Streams はデフォルトで圧縮をサポートしていないため、意図せずストリーミングの利点を失う可能性があります。
eslint-plugin-react-hooks v6
eslint-plugin-react-hooks@latest を公開しました。recommended preset では flat config がデフォルトになり、React Compiler 由来の新しいルールはオプトインになっています。レガシーな設定を使い続けるには recommended-legacy に変更してください。
- extends: [ 'plugin:react-hooks/recommended' ]
+ extends: [ 'plugin:react-hooks/recommended-legacy' ]
Compiler が有効にするルールの完全な一覧は linter docs を参照してください。変更の詳細は eslint-plugin-react-hooks の changelog をご確認ください。
useId のデフォルトプレフィックスを更新
19.2 では useId のデフォルトプレフィックスを :r:(19.0.0)や «r»(19.1.0)から _r_ に変更しました。もともと CSS セレクタに無効な特殊文字を使うのはユーザーが書いた ID と衝突しにくくするためでしたが、View Transitions をサポートするために useId で生成される ID を view-transition-name や XML 1.0 の名前として有効にする必要がありました。
Changelog(抜粋)
その他の注目変更
- react-dom: Allow nonce to be used on hoistable styles #32461
- react-dom: Warn for using a React owned node as a Container if it also has text content #32774
重要なバグ修正
- react: Stringify context as “SomeContext” instead of “SomeContext.Provider” #33507
- react: Fix infinite useDeferredValue loop in popstate event #32821
- react: Fix a bug when an initial value was passed to useDeferredValue #34376
- react: Fix a crash when submitting forms with Client Actions #33055
- react: Hide/unhide the content of dehydrated suspense boundaries if they resuspend #32900
- react: Avoid stack overflow on wide trees during Hot Reload #34145
- react: Improve component stacks in various places #33629, #33724, #32735, #33723
- react: Fix a bug with React.use inside React.lazy-ed Component #33941
- react-dom: Stop warning when ARIA 1.3 attributes are used #34264
- react-dom: Fix a bug with deeply nested Suspense inside Suspense fallbacks #33467
- react-dom: Avoid hanging when suspending after aborting while rendering #34192
完全な変更一覧は Changelog を参照してください。
Thanks to Ricky Hanlon for writing this post.