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.
マウント、更新、アンマウントフェーズ — 各段階でどのhooksとエフェクトが発火するか。
すべてのReactコンポーネントは3つのライフサイクルフェーズを経ます:マウント(DOMへの最初のレンダリング)、更新(stateまたはpropの変更による再レンダリング)、アンマウント(DOMからの削除)。Hooksを使用すると、これらのフェーズはクラスのライフサイクルメソッドではなく、useEffect、useLayoutEffect、クリーンアップ関数を通じて表現されます。
ReactはFunction Componentを呼び出し、JSXを評価し、結果のDOMノードを挿入します。useStateイニシャライザーが実行され、useRefオブジェクトが作成されます。DOM挿入後、useLayoutEffectが同期的に実行され、その後useEffectが描画後に非同期に実行されます。
setStateまたは新しいpropが到着すると、Reactは再レンダリングをキューに入れます。再レンダリング中、Hooksはマウント時と同じ順序で呼び出されます。ReactはDOMの新旧の出力を比較してパッチを適用します。エフェクトを再実行する前に、Reactは前のレンダリングのエフェクトからクリーンアップ関数を実行します。
エフェクトが再実行されるたびに(およびアンマウント時に)、Reactは前のエフェクトのクリーンアップ関数を呼び出します。これはタイマーのクリア、フェッチのキャンセル、イベントからのアンサブスクライブに不可欠です。
コンポーネントが削除されると(条件付きレンダリング、ルート変更など)、Reactはすべてのクリーンアップ関数を実行します — useEffectのクリーンアップ、次にuseLayoutEffectのクリーンアップ。DOMノードはドキュメントから削除されます。
空の依存配列:マウント後に1回実行。クリーンアップはアンマウント時に実行。componentDidMount + componentWillUnmountに相当します。
マウント後およびdepが変更されるたびに実行。クリーンアップは再実行前と、アンマウント時に実行されます。
依存配列なし:すべてのレンダリング後に実行。通常これは望ましくありません — stateの変更をトリガーすると無限ループになります。
useEffectと同じシグネチャですが、DOM変更後、描画前に同期的に発火します。DOM測定に使用します。
useEffectコールバックから返される関数。Reactは次のエフェクト実行前とアンマウント時に呼び出します。
1function Component({ userId }) {2 const [data, setData] = useState(null);34 useEffect(() => {5 // Runs after mount and when userId changes6 let cancelled = false;78 fetch(`/api/user/${userId}`)9 .then(res => res.json())10 .then(json => {11 if (!cancelled) setData(json); // guard against stale closure12 });1314 // Cleanup: cancel if userId changes before fetch resolves15 return () => { cancelled = true; };16 }, [userId]); // re-runs when userId changes1718 // Runs on unmount19 useEffect(() => {20 return () => console.log('Component unmounted');21 }, []);22}
ライフサイクルのタイミングを理解することで、最も一般的なReactバグを防げます:クリーンアップなしのデータフェッチ(アンマウントされたコンポーネントでのsetState警告を引き起こす)、タイマーのクリア忘れ(メモリリークを引き起こす)、依存配列の誤用によるエフェクトの実行頻度が多すぎるまたは少なすぎる問題。
A user profile page fetches user data on mount. If a user rapidly clicks through profiles (User A → User B → User C), the displayed data sometimes shows User A's data on User C's page.
Each navigation triggers a re-mount with a new userId. The useEffect fires 3 fetch calls, but they resolve out of order: User B (fast) → User C (fast) → User A (slow). The last-resolving fetch (User A) overwrites the correct data.
Use the cleanup function to set a `cancelled` flag. When userId changes, the previous effect's cleanup runs before the new effect, marking the old fetch as stale. The guard `if (!cancelled) setData(json)` prevents stale responses from updating state.
Takeaway: The cleanup function in useEffect isn't just for unmounting — it runs before EVERY re-execution of the effect. This makes it the perfect place to cancel async operations and prevent race conditions.
A chart component adds a `window.resize` event listener on mount to recalculate dimensions. After navigating away, the browser's memory usage keeps climbing and console shows 'setState on unmounted component' warnings.
The event listener was added in useEffect but never cleaned up. The component unmounts, but the listener still holds a reference to the component's state setter, creating a memory leak and triggering warnings on resize.
Return a cleanup function from useEffect that removes the event listener. The cleanup runs during the Unmount phase, before DOM removal, ensuring no orphaned listeners persist. Apply the same pattern for WebSocket connections, interval timers, and IntersectionObserver instances.
Takeaway: Every subscription, listener, or timer created in useEffect MUST have a corresponding cleanup. Think of mount/unmount as a pair: every 'set up' action needs a corresponding 'tear down' action.