Reactを学び始めた方の中には、useEffectというHookを見たことはあっても、その本当の使い所や挙動が分かりにくいという方が多いと思います。この記事では、useEffectとは何か、いつどのように使うか、クリーンアップ関数や依存配列などの重要なポイントまで、初心者の方が理解できるように丁寧に解説します。これを読めば、useEffectの使い方に自信が持てるようになります。
目次
React useEffectとは 使い方
useEffectはReactの関数コンポーネントで副作用を扱うためのHookです。副作用とはデータ取得、イベントリスナーの登録、タイマー、外部APIとの同期などコンポーネントのレンダー以外の処理を指します。useEffectを使うことで、コンポーネントがマウントされた際、更新された際、アンマウントされた際に任意の処理を行うことができます。
使い方としては、第一引数に副作用処理の関数(エフェクト)、第二引数に依存配列を渡します。依存配列を省略すると毎回実行、空配列だとマウント時とアンマウント時のみ、依存値を指定するとそれらが変化したときにだけ処理が実行されます。これにより無駄な再実行を防ぎパフォーマンスやバグの制御がしやすくなります。
useEffectの基本構文
useEffectは以下のように記述します。第一引数にコールバック関数、その中で副作用の処理を書き、オプションでクリーンアップ用の戻り値関数を返します。第二引数には依存配列を指定します。
例:
useEffect(() => {
// 副作用処理
return () => {
// クリーンアップ処理
};
}, [依存値1, 依存値2]);
依存配列の役割と挙動
依存配列はuseEffectがいつ再実行されるかを制御するためのものです。配列を省略するとコンポーネントのレンダーごとに実行され、空配列だと初回マウントとアンマウント時にのみ実行されます。依存値を入れると、それらが変化した時にだけ処理が走ります。ただし関数やオブジェクトを依存値にすると、その都度再生成されることにより思わぬ再実行が発生する場合があります。
クリーンアップ関数とは何か
クリーンアップ関数は、useEffectのコールバック内でreturnされた関数で、以前の副作用を解除したり整理したりするためのものです。これが無いとイベントリスナーが残ったり、タイマーが消えなかったり、古いネットワークリクエストの結果が不要なタイミングで反映されたりして、メモリリークや不具合の原因になります。
ライフサイクルとの関連性
useEffectを正しく理解するには、Reactのコンポーネントライフサイクルとの対応が重要です。マウント、更新、アンマウントのタイミングでどのようにuseEffectとクリーンアップが動くかを知ることで、意図した動作を実現できます。特に更新時に依存値が変わる時にクリーンアップが先に実行され、その後エフェクトが再設定されるという挙動がポイントです。
useEffectの使い方を理解するための具体例とパターン
ここでは実際にuseEffectを活用するパターンを紹介します。簡単な例からやや複雑なものまで取り上げ、どのように書くのが最適かを考えます。実践的な使い方を通じて、useEffectの使い方が明確になるように解説します。
データフェッチのパターン
APIからデータを取得したい場合、useEffect内でfetchなどの非同期処理を行い、依存配列に適切な値を設定します。例えばユーザーIDが変わったときに再取得したい場合、依存配列にそのIDを入れます。また、ネットワークリクエストが古くなっても結果が処理されないように、キャッシュやAbortControllerを使ってキャンセルするパターンが推奨されます。
イベントリスナーやタイマーの登録と解除
ウィンドウのスクロール監視やマウスの動き、キー押下などのイベントを登録する場合、useEffectでaddEventListenerを行い、クリーンアップでremoveEventListenerを行う必要があります。同様にsetIntervalやsetTimeoutを使う場合は、タイマーIDを保持してクリーンアップでclearInterval/clearTimeoutを行うことが重要です。これによりアンマウント後の不要な処理を防げます。
依存値の変更に応じた副作用の制御
依存値を慎重に選ぶことで、副作用の再実行を制御できます。例えばstateやpropsの中で頻繁に変化するものを依存配列に含めるとエフェクトが頻繁に再実行されます。不要なオブジェクトや関数を依存配列に含めないようにしたり、useCallbackやuseMemoでメモ化するなどして再現の頻度を抑える手法があります。
React useEffectの注意点とトラブルシューティング
使っていく中で陥りやすいミスや、パフォーマンス・バグの原因になるポイントがあります。ここでは代表的な注意点と、問題が起きたときの対処法を説明します。
無限ループになる原因と対策
依存配列にstate更新や関数などを入れすぎると、レンダー→エフェクト実行→依存値の更新→またレンダー、という無限ループが発生することがあります。特に関数やオブジェクトを依存に含めると、新しいインスタンスが毎回生成されるため頻繁に変化したとみなされます。対策として、依存値を整理する、関数をuseCallbackでメモ化することなどが有効です。
クリーンアップ忘れによるメモリリークや副作用の残留
イベントリスナーやタイマー、WebSocketなどのサブスクリプションを設定したとき、コンポーネントがアンマウントされた後もそれらが残っているとメモリリークやエラーを引き起こします。useEffectでsetupするなら必ずクリーンアップを返し、アンマウント時または依存値変化時に解除処理を行うようにします。
Strict Modeでの挙動の違い
開発環境ではStrict Modeが有効なプロジェクトが多く、マウント直後に一度Effectが実行された後クリーンアップが走り、さらに再度Effectが実行されるという動作が確認されます。この動作はバグ検出を目的としたもので、本番環境にはこの余分なクリーンアップは含まれません。クリーンアップがセットアップの逆になるように設計することで、開発と本番で整合的な動作を得られます。
パフォーマンスを意識したuseEffectの最適化テクニック
useEffectは便利ですが、副作用やクリーンアップの処理が重くなったり頻繁に呼び出されたりするとパフォーマンスに影響します。ここでは効率よく使うためのテクニックを紹介します。
useMemo/useCallbackとの組み合わせ
依存配列に関数やオブジェクトを直接書くと、それらは毎レンダーで再生成されるため依存値が常に変化し、Effectが頻繁に実行されてしまいます。useCallbackで関数をメモ化したり、useMemoでオブジェクトを生成しておくことで、不要な再実行を抑えられます。
必要な依存値だけを指定する
依存配列には、本当にEffectで参照していて変化する可能性がある値だけを入れるようにします。過剰に指定すると再実行が増え、少なすぎると古い値を参照してしまう問題が生じます。静的な値であれば外部に移動させたり、Effectの外に出すことで依存から除外できます。
遅延実行や分割実行の活用
重い処理をEffectで行う必要がある場合、setTimeoutやrequestAnimationFrameを使って少し遅らせたり、処理を複数のEffectに分割することで、一度に多くの作業が走らないようにするのが有効です。また非同期処理のキャンセル処理を適切に行い、古い処理の結果が誤って反映されることを防ぎます。
最新情報とReactバージョンでの変化
Reactの最新版ではuseEffectに関する仕様や挙動で特に注目すべき改善点や明確化がなされています。副作用管理のルールやクリーンアップの振る舞い、開発モードでのStrict Modeの挙動など、最新のドキュメントに基づいたポイントを押さえておきます。
Strict Modeでの重複実行の意図
開発モードでStrict Modeが有効な場合、コンポーネントがマウントされた後、Effectが走り、クリーンアップされ、再度Effectが走るという挙動があります。これはクリーンアップロジックがちゃんと機能するかを検証するための仕組みであり、仕様として明確になっています。これにより副作用が適切に管理できていないコードの発見が容易になります。
サーバーサイドレンダリングとの関係
サーバーサイドレンダリング(SSR)を用いている場合、useEffectはサーバー上では実行されずクライアントがマウントされた後にのみ動作します。初期レンダー時はクライアント側との相違が起こらないように注意が必要です。例えばlocalStorageの読み書きやwindowオブジェクトを使うような処理はuseEffectの中に入れる必要があります。
新しいHooksとの共存:useEffectEventなど
最新のReactではuseEffectだけでなく、useEffectEventなど別のHookを組み合わせて使うことで、依存配列の課題を回避するパターンが増えています。これにより最新のpropsやstateを参照するが依存値として含めたくない場合など柔軟な設計が可能です。必要に応じてこれらを導入することでコードの可読性と保守性が向上します。
よくある質問(FAQ)
初心者の方がuseEffectを使うときによく出る疑問をピックアップして、それぞれに答えます。ここを読むことで混乱しやすい点が整理できます。
useEffectを呼び出すタイミングは?
コンポーネントがマウントされた直後と依存配列の値が変更された後に呼び出されます。空配列ならマウント時のみです。また依存値を指定しなかった場合は状態やpropsが変わるたびに毎回呼び出されます。これらはレンダー後の処理なので、描画はブロックされません。
クリーンアップ関数がいつ実行されるか?
依存配列のいずれかの値が変わる際、古いEffectのクリーンアップが走ります。またコンポーネントがアンマウントされる際にも最後にクリーンアップが呼ばれます。開発環境ではStrict Modeにより、さらに1回多く実行されることがあります。
依存配列を空配列にするとどうなるか?
依存配列を空にするとEffectは一度だけマウント時に実行され、それ以降は再実行されません。クリーンアップはアンマウント時にのみ呼び出されます。このパターンは初期化処理やリスナーの登録/解除をマウント時のみ行いたい場合に有効です。
まとめ
ReactのuseEffectは、関数コンポーネントで副作用を制御するための強力なHookです。データ取得やイベント登録、タイマー設定など、コンポーネントのレンダー外で発生する処理を安全に扱うことができます。
依存配列とクリーンアップ関数の役割を正しく理解することが、バグ防止やパフォーマンス改善の鍵になります。特に依存値の指定漏れや過剰な指定、クリーンアップの忘れなどはよくあるトラブルですので注意が必要です。
最新のReactではStrict Modeの挙動や新しいHookとの連携など、副作用管理がより明確になっています。これらを押さえておくことで保守性と信頼性の高いReactアプリケーションが構築できます。
コメント