useEffectEvent:Effect内イベントハンドラーと依存関係の分離
useEffectEventは、React 19.2で新たに導入されたフックで、Effect(副作用)内で発生する「イベント的な処理(=Effect Event)」を、従来のuseEffectの依存配列ロジックから切り離して扱えるようにするものです。
useEffectの問題
以下のように、時計を表示するコンポーネントを考えます。ここでmakeClockは指定されたタイムゾーンの現在日時を返す関数だとします。
function makeClock(timeZone) {
return new Date().toLocaleString("ja-JP", { timeZone });
}
useEffect(() => {
console.log(`mounted: useEffectEvent なし`);
const interval = setInterval(() => { // (1) タイマーイベント
// (1) イベントハンドラー内で時計を更新
setClock(makeClock(timeZone));
}, 1000);
return () => {
// (1) タイマーをクリーンアップ
console.log(`cleanup: useEffectEvent なし`);
clearInterval(interval);
};
}, [timeZone]); // (2) timeZone が変わるたびに effect を再実行
// ↑ (3) timeZone を依存配列に含めないと、makeClockの引数のtimeZoneが更新されない
このコードを実行したときの挙動を図6に示します。
Consoleのログを見ると、timeZoneプロパティが変わるたびにEffectが再実行されていることがわかります(1)。
このtimeZoneの変更により、タイマーイベントが不必要に中断され、更新周期が乱れることになります。また、仮にtimeZoneが頻繁に変わる場合などは、パフォーマンス低下が発生する可能性もあります。
ここで注目すべきは、useEffectの依存関係に書かれた変数(timeZone、(2))が、実際にはEffect内では参照されず、タイマーイベント内でのみ参照される点です。
ではuseEffectの依存関係からtimeZoneを外せばいいのでしょうか(3)。この場合、たしかにタイマーの再設定は発生しなくなりますが、今度はtimeZoneが変わってもタイマーイベント内で参照される値が更新されないため、常に最初に渡されたタイムゾーンのままになってしまい、正しい動作が得られなくなります。
useEffectEventの活用
ここで登場するのがuseEffectEventです。これを使うと、先の問題をうまく解決できます。以下のように書き換えてみましょう。
// (1) useEffectEvent を使ってイベントハンドラーを定義
const updateClock = useEffectEvent(() => {
setClock(makeClock(timeZone)); // 常に最新の timeZone を参照できる
});
useEffect(() => {
console.log(`mounted: useEffectEvent あり`);
const interval = setInterval(() => {
// (2) useEffectEvent で定義した関数を呼び出す
updateClock();
}, 1000);
return () => {
console.log(`cleanup: useEffectEvent あり`);
clearInterval(interval);
};
}, []); // (3) timeZone を依存配列に含めない
useEffectEventを使ってupdateClock関数を定義します(1)。この関数は常に最新のtimeZoneを参照できるため、timeZoneが変わっても正しい値で時計を更新することができます。
タイマーのイベントハンドラーからはこのupdateClock関数を呼び出します(2)。ここで、useEffectの依存配列にはtimeZoneやupdateClockを含めないことが重要です(3)。useEffectEventを使って定義された関数はuseEffectの依存関係に含める必要がありません。
useEffectEvent使ったこのコンポーネントを実際に動かしてみたのが図7です。
UIの挙動は変わっていませんが、Consoleのログを見ると、最初の1回だけEffectが実行されていることがわかります。timeZoneが変わってもEffectの再実行は発生せず、タイマーイベントも中断されないため、安定した更新が行われています。
useEffectEventのユースケースと注意点
以上の特徴から、useEffectEventは上記のタイマーイベントのほか、ネットワーク通信のコールバック関数やaddEventListenerで登録するイベントハンドラーなど、Effect内で発生するイベントから呼び出される場合に有効です。逆にユーザーイベント(クリックイベントなど)には使用しません。
また、useEffectEventで定義した関数は、エフェクト内部からのみ呼び出せます。先に述べたように、useEffectEventで定義した関数はuseEffectの依存配列に含めないようする必要があります。これを誤ると、再び不必要なEffectの再実行が発生してしまいます。
これを防ぐために、React 19.2ではeslint-plugin-react-hooksがバージョン6に更新され、useEffectEventの使用に関するLintルールが追加されています。これにより、誤った依存関係の指定を防止できるため、ぜひ導入を検討してください。
Performance Tracks: Chrome DevToolsでのReactパフォーマンス分析の強化
React 19.2では、Chrome DevToolsのPerformanceタブに「Scheduler」と「Component」トラックが追加され、Reactのレンダリングパフォーマンスを詳細に分析できるようになりました。
パフォーマンスは開発者ツールを開き、Performanceタブを選択して、Record(記録)ボタンで測定します。実際に今回のサンプルコード02を実行したときのパフォーマンスプロファイルを図8に示します。
SchedulerトラックはReactのスケジューリングイベントを示しており、RenderやEffectがどのタイミングで走っているかがわかります。また、Componentトラックは各コンポーネントのレンダリング時間を示しており、Appをはじめ、各コンポーネント(MyClock1とMyClock2)のレンダリング時間がそれぞれ表示されているのがわかります。
これにより、各コンポーネントのレンダリング時間や更新頻度、優先度などを視覚的に把握できるようになり、パフォーマンスボトルネックの特定や最適化が容易になります。
まとめ
React 19.2では、<Activity>やuseEffectEventなど、UIの表示制御や副作用ロジックの整理に役立つ新しい選択肢が提供されました。特に<Activity>は、個人的にもこれまで条件付きレンダリングで苦労してきた部分を大幅に改善できると感じています。使い方もシンプルなので、既存プロジェクトへの導入も比較的容易でしょう。
useEffectEventも、Effect内のイベント処理を整理するのに役立ちます。Lintルールも整備されているため、誤用を防ぎつつ導入できる点も安心です。Performance Tracksも、パフォーマンス分析の強力なツールとして活用でき、開発者にとってとても有益です。
これまでに紹介した機能に加えて、React 19.2では以下のような新機能や改善点も導入されています。
- cacheSignal:Reactのcache APIにキャッシュが不要になったときのシグナルを返す関数が追加され、無駄な処理を抑制できるようになりました。
- Partial Pre-rendering(部分的な事前レンダリング):アプリの静的部分を事前にレンダリングして CDN から提供し、その後残りのレンダリングを再開して動的コンテンツを埋め込むことができます。
- Suspenseバッチング:従来、SSR(ストリーミングレンダリング)で複数の Suspense 境界が連続していた場合、それぞれが別々にレンダリング順序どおりに出されることがありましたが、これらをバッチ(一括)で表示する遅延制御が導入されました。
-
useIdの接頭辞変更:
useIdフックで生成されるIDの接頭辞が変更されました。 - Web Streams対応:Node.jsでSSRをストリーミングするためのWeb Streamsのサポートが追加されました。
これらについては、次回の記事で詳しく解説する予定です。React 19.2の新機能を活用して、より良い開発者体験とユーザー体験を実現していきましょう。
