Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
エフェクトの実行タイミング、クリーンアップの順序、依存関係の追跡。
useEffectは、Reactコンポーネントと外部システム(ブラウザAPI、サードパーティライブラリ、ネットワークリクエスト、サブスクリプション)を同期するための主要なHookです。ブラウザが画面を描画した後に実行されるため、非ブロッキングです。すべてのエフェクトは、Reactが次のエフェクト実行前とコンポーネントのアンマウント時に呼び出すクリーンアップ関数をオプションで返します。
ReactはDOMの変更をコミットし、ブラウザが画面を描画し、その後ReactはuseEffectコールバックを非同期に実行します。この順序により、エフェクトは最初のレンダリングを決してブロックしません — ユーザーはすぐにコンテンツを見ます。
ReactはObject.is()等値比較を使用して、現在の依存配列と前のレンダリングの依存配列を比較します。いずれかの依存関係が変わった場合、エフェクトは再実行がスケジュールされます。依存配列なし = すべてのレンダリング後に実行。空配列[] = マウント後に1回実行。
エフェクトが関数を返す場合、Reactはそれをクリーンアップとして格納します。(依存関係が変わったため)エフェクトを再実行する前に、Reactは前のクリーンアップを先に呼び出します。コンポーネントがアンマウントされると、Reactは最後のクリーンアップを呼び出します。
StrictModeでは、Reactは適切にクリーンアップしないエフェクトを検出するために意図的にすべてのコンポーネントをマウント→アンマウント→再マウントします。二重呼び出しは開発環境でのみ行われます。これが適切なクリーンアップが必須である理由です。
useLayoutEffectはuseEffectと同一のセマンティクスを持ちますが、DOM変更後、ブラウザが描画する前に同期的に発火します。エフェクトがDOM測定を読み取ってユーザーが何かを見る前に修正を適用する必要がある場合に使用します。
再実行頻度を制御します。[] = マウント時のみ。[a, b] = aまたはbが変わったとき。配列なし = すべてのレンダリング。ReactはObject.is()で比較します — オブジェクト・配列を依存関係にするのは避けてください。
エフェクトから返される関数。エフェクトが再実行される前(依存関係が変わった場合)とコンポーネントのアンマウント時に呼び出されます。フェッチのキャンセル、タイマーのクリア、アンサブスクライブに使用します。
エフェクトが古いレンダリングのstate・propsをキャプチャしているが、更新された値を取得するために再実行されない場合。変数を依存関係に追加するかrefを使用することで修正できます。
DOM変更後、描画前に同期的に発火します。描画をブロックします — 高速に保ってください。DOM測定(getBoundingClientRect、スクロール位置)に使用します。
DOM変更前に発火します。CSS-in-JSライブラリがレイアウト前にスタイルルールを同期的に注入するためのものです。一般的なアプリケーションコード向けではありません。
1// 1. Event subscription — must clean up2useEffect(() => {3 window.addEventListener('resize', handleResize);4 return () => window.removeEventListener('resize', handleResize);5}, []); // mount once, clean up on unmount67// 2. Fetch with cancellation (AbortController)8useEffect(() => {9 const controller = new AbortController();1011 fetch('/api/data', { signal: controller.signal })12 .then(res => res.json())13 .then(setData)14 .catch(err => {15 if (err.name !== 'AbortError') throw err; // ignore cancellation16 });1718 return () => controller.abort(); // cancel if dep changes or unmount19}, [userId]);2021// 3. useLayoutEffect for DOM measurement22useLayoutEffect(() => {23 const height = ref.current.getBoundingClientRect().height;24 setContainerHeight(height); // applied before paint — no flicker25}, [content]);
useEffectはReactで最も誤用されているHookです。エフェクトを正しく使う — 正しい依存関係、適切なクリーンアップ、正しいタイミング — ことは、メモリリークせず、余分なネットワークリクエストを行わず、古いデータのバグがないアプリを構築するために不可欠です。思考モデルの転換が必要です:「エフェクトはライフサイクルイベント」ではなく「エフェクトは同期メカニズム」です。マウント・更新・アンマウントのコールバックにフックするのではなく、外の世界と同期し続ける方法をReactに伝えています。
A developer writes `useEffect(() => { fetchData() })` without a dependency array. The app makes thousands of API calls per second, the browser freezes, and the API rate limits the user.
No dependency array means the effect runs AFTER EVERY RENDER. fetchData sets state → state change triggers re-render → re-render fires the effect → effect calls fetchData → sets state → infinite loop. Each loop iteration also creates a new network request.
Add the correct dependency array: `useEffect(() => { fetchData() }, [])` for mount-only, or `useEffect(() => { fetchData(query) }, [query])` for re-fetch when query changes. The dependency array tells React's effect scheduler when to skip re-execution.
Takeaway: The dependency array is not optional decoration — it's the core control mechanism of useEffect. No array = every render. Empty array = mount only. Specific deps = run when those values change. Getting this wrong is the #1 source of useEffect bugs.
A countdown timer displays the time remaining. You use `setInterval` inside useEffect with an empty dependency array. The timer shows the same number forever instead of counting down.
The useEffect callback closes over the initial `count` value (e.g., 60). setInterval calls `setCount(count - 1)` every second, but `count` is always 60 inside the closure (captured at mount time). Result: `setCount(59)` every second — never changes from 59.
Use the functional updater form: `setCount(prev => prev - 1)`. The updater receives the CURRENT state, not the stale closure value. Alternatively, store count in a ref and sync it every render. The functional form is React's escape hatch for stale closures in effects.
Takeaway: Effects capture values at the time they're created (closures). For intervals and timeouts that need current state, always use the functional updater `setState(prev => ...)` or a ref pattern to bypass the stale closure.