Reactの関数コンポーネントを使っていると、非同期処理やイベントの登録、副作用と呼ばれる処理を行いたくなる場面が出てきます。そんなときに使うのがuseEffectフックです。この記事では、React useEffectとは 使い方というキーワードにフォーカスし、基礎から実践まで、副作用の発生タイミングや依存関係の制御、よくある落とし穴などをていねいに解説します。最新情報も取り入れて、信頼性の高い内容です。
目次
React useEffectとは 使い方の基礎
ReactのuseEffectは、関数コンポーネント内で副作用(サイドエフェクト)を扱うためのフックです。たとえばAPIからデータを取得したり、イベントリスナを登録したり、タイマーを設定したりする処理を行うことができます。レンダリング後に処理が実行され、必要ならクリーンアップ(後片付け)関数を返すことができます。
また、依存配列(dependencies)を指定することで、いつ副作用を再実行するかを制御できます。依存配列を省略するとレンダリングのたびに実行され、空配列であればマウント時のみ、特定の状態やプロップスの変化で再実行されます。これにより、パフォーマンスや予期せぬ副作用を防ぐことができます。
さらに、Strict Modeが有効な開発環境では、useEffectがマウント時に一度セットアップとクリーンアップ→再度セットアップを行う挙動があります。これは開発中の副作用処理の不備を検出するための仕様で、本番環境では行われません。これらの仕組みを理解することが、React useEffectとは 使い方をマスターする第一歩です。
useEffectの構文と引数
useEffectは大きく二つの引数を取ります。一つ目は callback 関数で、副作用のロジックを含みます。必要ならばこの callback がクリーンアップ用の関数を返します。二つ目は依存配列で、関数内で参照する props や state を指定します。依存配列が空の場合はマウント時のみ実行され、配列省略時はレンダリングごとに毎回実行されます。
例えば、以下のような形です:
useEffect(() ⇒ {
// 副作用の処理
}, [依存する変数1, 変数2]);
この構文によって、いつ callback が再実行され、いつクリーンアップ処理が走るかが決まります。依存関係を正しく指定することが非常に重要です。
副作用(side effect)の定義と例
副作用とはコンポーネントの外部に影響を与える処理のことを指します。DOM操作、API呼び出し、ブラウザのストレージ操作、イベントの登録やサブスクリプションなどが典型例です。状態の変更だけで完結するような処理や、描画結果に依存する純粋な演算は副作用とはみなされません。
具体例を挙げると、APIからデータを取得して state を更新する処理、タイマーを設定して定期的な処理を行う処理、ウィンドウサイズの変更を監視するイベントリスナを追加する処理などがそれにあたります。こうした処理は cleanup を忘れるとメモリリークやイベントの重複登録といった不具合を招きます。
Strict Modeで副作用が2回実行される理由
最新のReactでは、開発モードでStrict Modeが有効な場合、コンポーネントのマウント時にset up(セットアップ)とクリーンアップを一度行ったあと、再度セットアップが行われます。開発中の不具合を早期に発見するための仕組みであり、本番環境ではこの挙動は起こりません。
この挙動により、クリーンアップ関数が正しく実装されていない場合に副作用が重複する問題、新しい接続やイベントの削除忘れなどのバグが浮き彫りになります。開発者はこの仕様を理解し、クリーンアップを確実に行うことで不具合を防ぐことができます。
実践的なReact useEffectとは 使い方パターン
useEffectの基本構造を理解したあとは、実践的な使い方に移ります。ここでは代表的なパターンを複数紹介し、それぞれのパターンごとに依存関係の指定やクリーンアップの方法、副作用の制御方法を解説します。これにより、React useEffectとは 使い方というテーマに対する理解がぐっと深まります。
マウント時にデータを取得するパターン
コンポーネントが画面に初めて表示されたときに API などからデータを取得する処理を useEffect で書くことがよくあります。このとき、依存配列に空配列 [] を指定することでマウント時だけ実行されます。
例:
useEffect(() ⇒ {
fetchData().then(data ⇒ setState(data));
return () ⇒ {
キャンセルなどのクリーンアップ処理;
}
}, []);
このパターンでは、fetch中にコンポーネントがアンマウントされた場合を考えてキャンセル処理を行うなど、クリーンアップが重要です。最新のReactでは AbortController を使って fetch をキャンセルすることが推奨されます。
依存する値が変化したときに処理するパターン
state や props の値が変更されたときに処理を実行したい場合、依存配列にその値を入れます。例えば、あるプロップスが変わるたびに API を再フェッチしたい場合などに使います。
例:
useEffect(() ⇒ {
fetch(`/api/items?filter=${filter}`).then(…);
return () ⇒ {
キャンセル処理;
}
}, [filter]);
このように依存値を指定すると、その値が前回レンダー時と異なるときのみ callback が再実行されます。依存配列に関数やオブジェクトを入れる際は参照の比較に注意が必要です。
タイマー・イベント・購読のパターン
定期実行のタイマー設定、ウィンドウリサイズ等のイベント登録、WebSocketやサブスクリプションなど、外部との相互作用を持つ処理も useEffect で扱います。こうした処理は必ずクリーンアップ関数を返して、アンマウント時や依存値が変化したときに登録を解除する必要があります。
例:
useEffect(() ⇒ {
const id = setInterval(() ⇒ {
…定期処理…;
}, 1000);
return () ⇒ {
clearInterval(id);
}
}, []);
この例では setInterval をセットし、クリーンアップで clearInterval を行うことで、メモリリークや不要な実行を防ぎます。イベントリスナ登録では removeEventListener などを使います。
依存関係と副作用制御の詳細
副作用を正しく制御するためには依存関係(dependency array)の使い方を理解することが最も重要です。ここでは、依存配列の三つのパターン、関数やオブジェクトを依存配列に入れる際の注意点、useLayoutEffectとの使い分けなど、より細かい制御方法を見ていきます。
依存配列なし・空・値ありの違い
依存配列をまったく書かないと、レンダーごとに callback が実行され続けます。依存配列が空 [] の場合はマウント時のみ一回だけ実行され、本番環境でもそれ以外では再実行されません。特定の変数を入れた場合は、その変数が変化するたびに再実行されます。これら三つのパターンを正しく理解することで、不要な再レンダーや無限ループを避けられます。
関数・オブジェクトを依存値に含める落とし穴
依存配列に関数やオブジェクトを直接書くと、毎回新しい参照になるため、Effect が毎レンダーで再実行されることがあります。これはパフォーマンスの低下や副作用の重複実行につながります。
対処法としては、関数やオブジェクトをuseCallback や useMemo を使ってメモ化する/Effectの中で生成する/依存関係自体をシンプルに保つ、などがあります。
クリーンアップ処理の重要性
useEffect の callback が何らかの登録処理や非同期処理を行う場合、返り値としてクリーンアップ関数を指定することが重要です。コンポーネントのアンマウント時だけでなく依存値変更時にも古い副作用を取り除く必要があります。
クリーンアップ処理の典型例は、タイマーの停止、イベントリスナの解除、WebSocket 接続の切断、fetch 処理の中断などです。特に AbortController を使った fetch のキャンセルや、イベント登録解除の忘れはバグの原因になります。
useEffectとuseLayoutEffectの違い
useEffect はブラウザのレンダリング後に非同期で実行されるのに対し、useLayoutEffect はDOMが画面に反映される前に同期的に実行されます。レイアウトの計測や位置調整など、ユーザーが視覚的に不自然を感じる前に DOM 操作を行いたい場合は useLayoutEffect を使います。
ただし、ほとんどの場合は useEffect で十分です。視覚的なチラつき(フリッカー)が問題になる場面のみ useLayoutEffect を検討すればよく、むやみに使うと処理がブロッキングされパフォーマンスに影響することがあります。
よくある失敗と避ける方法
useEffect を使いこなしていないと、副作用の重複実行、無限ループ、依存値の不足・過剰指定など、多くのトラブルが発生します。ここでは実際によく起きる失敗パターンを挙げ、それぞれの原因と対応策を解説します。
無依存配列での無限ループ
依存配列を省略した状態で、callback 内で state を更新するとレンダー → 副作用 → state 更新 → 再レンダーというループになります。これは無限ループを招き、アプリがクラッシュする可能性があります。
解決策は依存配列を正しく設定すること、あるいは state 更新処理を関数形式(prev ⇒ new)で書くことなどです。state 更新関数が依存値として不要になるケースが多いため、依存配列を空または限定的にする設計が重要です。
依存値の不足による古い値参照(Stale Closure)
依存配列に必要な値を入れ忘れると、callback 内で古い値を参照してしまうことがあります。これを stale closure(古いクロージャ)と呼び、デバッグが難しいバグの原因になります。
ESLint の react-hooks/exhaustive-deps ルールを有効にすると、多くの場合こうした問題を事前に警告してくれます。警告を無視せず、依存値を正確に指定する習慣が大切です。
依存値の過剰指定による不要な再実行
逆に、関数やオブジェクトなど毎レンダーで新しい参照になるものを依存値として入れると、副作用が毎回実行されることがあります。これもアプリの性能を悪化させる原因です。
この問題を避けるには useMemo や useCallback を使って参照を安定させる、あるいは依存値を最小限にする設計を意識することが重要です。
Strict Mode に起因する予期せぬ実行回数
前述のように開発モードで Strict Mode が有効だと、useEffect がマウント時に一度セットアップとクリーンアップを行い、その後もう一度セットアップされます。これを原因とする二重フェッチやイベント重複登録がよく報告されます。
イベント登録や API 呼び出しなど重要な副作用がある場合、クリーンアップを漏れなく記述し、fetch を使う場合は AbortController でキャンセル処理を入れるなどの対策をとります。これにより開発時の挙動と本番時の挙動の整合性が保てます。
React useEffectとは 使い方に関する高度な応用
基礎と典型パターンを押さえたら、より応用的な使い方にも触れておきましょう。ここではカスタムフックの活用、複数の副作用を分割する設計、例外的な状況での処理、パフォーマンス改善などを紹介します。
カスタムフックで副作用ロジックを抽象化
もし同様の副作用処理を複数のコンポーネントで使うなら、カスタムフックにまとめることで可読性・再利用性が向上します。例えば API フェッチ処理やイベント監視処理を useFetch や useEventListener のような名前で汎用化できます。
カスタムフックでは内部で useEffect を使い、外部から依存値を受け取る設計にすることで柔軟性が保てます。依存値の変化に対する適切なクリーンアップも行うことで、副作用の漏れがない構造にできます。
条件付き実行と early return の使い方
副作用を実行する条件がある場合、useEffect 内で早期リターン(if 文など)を使って処理を制御することができます。たとえば、特定のプロップスが未定義のときは処理をスキップするなどです。
依存値が複数ある場合、それぞれの変化を検査して無駄な処理を防ぐ工夫も可能です。条件付きで early return を入れることで、依存関係の変化以外で無駄な副作用が走るのを防ぎます。
パフォーマンス改善のヒント
副作用処理が重いものだったり、頻繁に再実行される可能性があるときは、処理の回数を抑える設計が重要です。依存配列の最小化、関数・オブジェクトのメモ化、Debounce/Throttle の利用などが有効です。
APIフェッチの頻度を減らす、レンダリングを最小限に抑える工夫、スクロールや入力イベントの処理を軽量にする工夫などが考えられます。これによりユーザー体験が滑らかになります。
例外的なケースでの対処法
たとえば SSR(サーバーサイドレンダリング)環境、あるいはレンダリングが中断・再開される concurrent rendering を考慮する必要があるときがあります。こうした環境では、DOM 操作や window 使用、localStorage 参照などがマウント前に実行されないように注意が必要です。
また、API のキャンセルや長時間の副作用がある場合、AbortController や signal を使った中断処理、または useEffectEvent のような仕組みを使って非反応的なコードを分離することも選択肢の一つになります。
React useEffectとは 使い方に関する最新のアップデートとベストプラクティス
React の進化に伴い、useEffect を取り巻く慣習や警告、Lint ルール、パフォーマンスに関する知見も更新されています。ここでは最新情報を交えたベストプラクティスを紹介し、React useEffectとは 使い方というテーマにおいて、安心してコードを書くための指針を示します。
Lint ルールと依存関係の警告
等依存関数ライブラリでは、useEffect 内で使用されている reactive な値が依存配列に全て含まれていないと警告を出すルールがあり、これを守ることで stale closure やバグを未然に防げます。警告を抑えようとして依存値を意図的に省略することは推奨されません。
依存関係に関しては、オブジェクトリテラルや関数が毎回再生成されてしまうと、参照が異なってしまい Effect の再実行が不要でも再実行されてしまいがちです。useCallback や useMemo によるメモ化を活用するとよいです。
AbortController による非同期処理のキャンセル
API 呼び出しを行う副作用の場合、コンポーネントがアンマウントされたり依存値が変化するタイミングで fetch を途中で止める手段を持つことが大事です。AbortController を使えば signal を用いて fetch のキャンセルが可能で、無駄なデータ受信やエラーの原因を抑制できます。
fetch に限らず、その他の非同期処理でも中断可能な構造を取り入れることが望ましく、途中でクリーニング処理が走るよう設計することで副作用を安全に扱えます。
React Strict Mode が本番コードに与える影響の最小化
Strict Mode によって開発時の副作用が二度実行される仕様を理解し、クリーンアップ処理を正しく書くことが鍵です。また、useEffect の中でプロップスや state の変化に応じた処理を行う設計では、依存関係を正しく設定することで本番環境での挙動と整合性を取ることができます。
もし二重実行による API の二重呼び出しなどが気になるなら、fetch のキャンセル機構や early return を使って副作用を整理し、同じ処理が重複しないように工夫することが求められます。
非反応的なイベント処理用の新しい API(useEffectEvent など)の活用
React の最新ドキュメントには、props や state の変化に反応させずに最新の値を参照できる非反応的なイベント用の API が紹介されており、副作用の依存配列で扱いづらい場面を安全に処理できるようになってきています。こうした API を活用することで、依存関係のあいまいさやステートの古い値参照の問題に対処できます。
例えば、ある処理を props の変化にだけ反応させたいが、state の変化には反応させたくないようなケースで、Effect と並行してそのようなイベント用 API を使えばコードが分かりやすく安全になります。
まとめ
React useEffectとは 使い方を理解するには、その目的、副作用のタイミング、依存配列の指定、クリーンアップ処理、Strict Mode の挙動などを踏まえて一歩ずつ学ぶことです。副作用を扱うことでコンポーネントが外部システムと連携できるようになる反面、誤用すると無限ループやバグ、パフォーマンス低下の原因にもなります。
まずは基本パターンを押さえ、依存配列を適切に指定すること、クリーンアップ関数を漏れなく書くこと、そして最新の React や Lint の推奨する書き方を取り入れることが重要です。実践を重ねることで安全で効率的な React アプリケーションが構築できるようになります。
コメント