React: useEffect とライフサイクル
最終更新:2026-10-26
useStateがコンポーネントの「メモリ」だとすれば、useEffectはその「カメラ」に相当します。これにより、コンポーネントが画面にレンダリングされた後(「写真を撮った後」)、データの取得、イベントの購読、DOMの操作、タイマーの設定といった追加の操作を行うことができます。
1. 学習内容
- useEffect の目的と基本的な使い方
- 配列の3つの活用パターン
- クリーンアップ関数(メモリリークを防ぐため)
useEffectとコンポーネントのライフサイクルとの関係- useEffect と useLayoutEffect の違い
2. データ読み込みページの物語
(1) 課題:データ要求により無限ループが発生する
アリスは、コンポーネントの読み込み時にユーザーデータを取得したいと考えているため、次のように記述します。
JSX
function UserProfile({ userId }) {
const [user, setUser] = React.useState(null)
// ❌ Error:Requesting data directly within a component's function body
fetch('/api/users/' + userId)
.then(res => res.json())
.then(data => setUser(data))
return <div>{user?.name}</div>
}
質問:
fetch→setUser(data)→ コンポーネントの再レンダリング- 再度レンダリング →
fetchを再度実行 → ステータスを再度設定 → 再度レンダリング... - 無限ループ! ページがフリーズしています。
アリスは、「コンポーネントがマウントされたときに一度だけ実行される」方法が必要です。
(2) useEffect による解決策
JSX
import { useState, useEffect } from 'react'
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
// ✅ Correct:useEffect + Empty array of dependencies
useEffect(() => {
fetch('/api/users/' + userId)
.then(res => {
if (!res.ok) throw new Error('Failed to load')
return res.json()
})
.then(data => {
setUser(data)
setLoading(false)
})
.catch(err => {
setError(err.message)
setLoading(false)
})
}, [userId]) // Only at userId Re-execute when changes occur
if (loading) return <p>Loading......</p>
if (error) return <p>Error:{error}</p>
return <h1>{user.name}</h1>
}
メリット:useEffect 副作用をレンダリングロジックから分離する――レンダリングはUIのみを担当し、副作用はレンダリング完了後に実行される。
3. useEffect の基礎
(1) 基本的な構文
JSX
useEffect(() => {
// Side Effect Logic(Execute after rendering is complete)
console.log('Component rendering complete!')
return () => {
// Cleanup Function(Run before unloading or re-executing a component)
console.log('Addressing Side Effects')
}
}, [Dependency Array])
| セクション | 説明 |
|---|---|
() => { ... } |
レンダリングが画面に反映された後に実行される副作用コールバック |
return () => { ... } |
クリーンアップ関数。コンポーネントのアンインストールや依存関係の変更により再実行が行われる前に実行される |
[dependencies] |
useEffect が再実行されるタイミングを制御する配列 |
graph TB
A[Component Rendering] --> B[React Submit DOM Update]
B --> C[useEffect Callback Execution]
C --> D{Changes in Dependencies?}
D -->|is | E[Execute the previous cleanup function]
E --> F[Re-execute useEffect]
D -->|No| G[Skip]
F --> H{Component Uninstallation?}
G --> H
H -->|is | I[Execute the cleanup function]
style C fill:#61dafb,color:#000
style I fill:#ff6b6b,color:#fff
(2) 依存関係配列の3つのパターン
JSX
// Pattern 1:Empty array [] — Execute only once when mounted
useEffect(() => {
console.log('Component Mounting')
fetch('/api/init').then(setData)
}, []) // Never re-execute
// Pattern 2:Dependencies [a, b] — Re-execute when dependencies change
useEffect(() => {
console.log('userId It has changed.:', userId)
fetch('/api/users/' + userId).then(setUser)
}, [userId]) // userId Re-execute when changes occur
// Pattern 3:No array dependencies — Executed on every render
useEffect(() => {
console.log('Executed on every render')
}) // Note:No array!
| 依存関係の配列 | 実行タイミング | ユースケース |
|---|---|---|
[] |
マウント時のみ | データ要求とサブスクリプションの初期化 |
[dep1, dep2] |
マウント時 + 依存関係が変更されたとき | パラメータに応じてデータが変更される |
| 無数のセット | レンダリングのたびに | ほとんど使われない(通常は他の手法に置き換えられる) |
4. クリーンアップ関数
クリーンアップ関数は、メモリリークやレースコンディションを防ぐ上で重要な役割を果たします。
(1) タイマーのリセット
▶ 試してみよう(2) イベントリスナーの整理
▶ 試してみよう(3) リクエストのキャンセル(レースコンディションを回避するため)
JSX
function SearchResults({ query }) {
const [results, setResults] = useState([])
useEffect(() => {
let cancelled = false
fetch('/api/search?q=' + query)
.then(res => res.json())
.then(data => {
// ✅ If the component has been uninstalled or query Has changed,Do not update status
if (!cancelled) {
setResults(data)
}
})
// ✅ Cleanup:Uncheck
return () => {
cancelled = true
}
}, [query])
return <div>{results.map(r => <p key={r.id}>{r.name}</p>)}</div>
}
// When users quickly switch search terms:
// 1. Search"A" → useEffect Execute
// 2. Search"AB" → Clear Function Markers cancelled=true → useEffect Re-execute
// 3. Search"A" the response came back → cancelled=true → Do not update status(Avoid flashing)
出力:
TEXT
サブタイトル:"useEffect Life Cycle Demonstration"。状態:count、logs。useEffect が副作用を管理。タイマーによる更新
▶ サンプル:クリーンアップ関数の包括的な実演
▶ 試してみよう出力:
TEXT
カウントは 2 秒ごとに自動で +1、スペースキーで +5。ログ:"⏱️ Timer started"。アンマウント → "⏱️ Timer cleared"、"⌨️ Keyboard listening removed"
5. useEffect をコンポーネントのライフサイクルに紐付ける
クラスコンポーネントの時代には、3つのライフサイクルフックがありましたが、フックの時代では、useEffect—1つのフックで3つの役割を果たします:
graph LR
subgraph "The Lifecycle of Class Components"
A1[componentDidMount] --> A2[componentDidUpdate]
A2 --> A3[componentWillUnmount]
end
subgraph "Hooks 's useEffect"
B1["useEffect(fn, [])<br/>Execute when mounted"]
B2["useEffect(fn, [dep])<br/>Mount + run when deps change"]
B3["return cleanup<br/>Clean up before uninstalling or rerunning"]
end
出力:
TEXT
サブタイトル:"useEffect Life Cycle Demonstration"。表示:"useEffect Life Cycle Demonstration"。状態:count(setter: setCount)、logs(setter: setLogs)。useEffect が副作用を管理。タイマーベースの更新
| クラスのコンポーネントのライフサイクル | 対応する useEffect |
|---|---|
componentDidMount |
useEffect(() => { ... }, []) |
componentDidUpdate |
useEffect(() => { ... })(無数のセット)または useEffect(() => { ... }, [dep]) |
componentWillUnmount のクリーンアップ関数 |
useEffect(() => { return () => { ... } }, []) |
▶ サンプル:1つのuseEffectで複数のライフサイクルフェーズを処理する
JSX
// ============================================
// Example:Chat Room Component——One useEffect Covering the Entire Lifecycle
// ============================================
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([])
useEffect(() => {
// === componentDidMount + componentDidUpdate ===
console.log(`🟢 Connect to the chat room:${roomId}`)
const connection = createConnection(roomId)
connection.connect()
connection.on('message', (msg) => {
setMessages(prev => [...prev, msg])
})
// === componentWillUnmount + Clean Up Before Re-Execution ===
return () => {
console.log(`🔴 Leave the chat room:${roomId}`)
connection.disconnect()
}
}, [roomId]) // roomId When things change:Disconnect the old connection → Join a New Chat Room
return (
<div>
<h3>Chat Room:{roomId}</h3>
{messages.map((msg, i) => <p key={i}>{msg}</p>}
</div>
)
}
// Usage
<ChatRoom roomId="general" /> // Connect general Chat Room
<ChatRoom roomId="random" /> // roomId Changes → Disconnect general → Connect random
// When a component is unloaded → Disconnect the current connection(Execution of the cleanup function)
出力:
TEXT
マウント:console "🟢 Connected to chat room: general"。roomId の切り替え:"🔴 Left: general" → "🟢 Connected: random"。アンマウント:切断。
6. useEffect と useLayoutEffect の違い
React には 2 つの「副作用」フックが用意されていますが、これらは実行タイミングが異なります。
graph LR
A[Trigger Rendering] --> B[Computational Virtual DOM]
B --> C[DOM Update]
C --> D{useLayoutEffect?}
D -->|is | E[Execute in parallel<br/>Blocking Browser Rendering]
D -->|No| F[Browser rendering complete]
F --> G[useEffect Asynchronous Execution]
style E fill:#faad14,color:#000
style G fill:#52c41a,color:#fff
| フック | 実行タイミング | レンダリングをブロックするか | 使用例 |
|---|---|---|---|
useEffect |
ブラウザのレンダリング後(非同期) | ❌ | 90%のシナリオ:データリクエスト、イベントリスナー、ロギング |
useLayoutEffect |
DOMの更新後、ブラウザによるレンダリングの前(同期) | ✅ | DOMの寸法を測定し、レイアウトを読み取り、アニメーションを同期させる |
ルール:まずは
useEffectと入力してください。「ちらつき」や「レイアウトの乱れ」といった問題が発生した場合にのみ、useLayoutEffectに切り替えてください。
7. 完全な例:データダッシュボード
▶ 実行例を展開(98 行)
出力:
TEXT
🟢 チャットルームに接続:...
🔴 チャットルームから退出:...
期待される出力:4つの統計カード、リアルタイム更新の切り替えボタン、ウィンドウサイズインジケーター、および最終更新時刻を含むデータダッシュボード。
▶ サンプル:useEffect を使用したリアルタイムデータ同期の実装
JSX
function LiveSearch({ apiBase }) {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => {
if (!query.trim()) { setResults([]); return }
setLoading(true)
const controller = new AbortController()
fetch(`${apiBase}/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })
.then(res => res.json())
.then(data => { setResults(data); setLoading(false) })
.catch(err => { if (err.name !== 'AbortError') setLoading(false) })
return () => controller.abort()
}, [query, apiBase])
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<input value={query} onChange={e => setQuery(e.target.value)}
placeholder="Search users..." style={{ width: '100%', padding: 8, borderRadius: 4, marginBottom: 8 }} />
{loading && <p>Searching...</p>}
<ul style={{ listStyle: 'none', padding: 0 }}>
{results.map(r => <li key={r.id} style={{ padding: 4, borderBottom: '1px solid #f0f0f0' }}>{r.name}</li>)}
</ul>
</div>
)
}
出力:
TEXT
useEffect が副作用を管理
出力:
TEXT
表示:"Searching..."。状態:query(setter: setQuery)、results(setter: setResults)、loading(setter: setLoading)。入力:Search users...。リスト:{r.name}。useEffect が副作用を管理。非同期データ取得/読み込み状態
▶ サンプル:useEffect を使ってウィンドウサイズの変更を監視する
▶ 試してみよう出力:
TEXT
useEffect が副作用を管理
出力:
TEXT
表示:"📱 Single column layout"。状態:size(setter: setSize)。useEffect が副作用を管理
▶ サンプル:useEffect と localStorage を使った状態の永続化
▶ 試してみよう出力:
TEXT
ToDo リストを localStorage に保存。タスクを追加し、クリックで切り替え(取り消し線)。ページを再読み込み → タスクは保持される。Enter キーで追加。
❓ よくある質問
Q
useEffectと、コンポーネント内に直接コードを記述する場合の違いは何ですか?A
useEffect内のコードはレンダリングが完了した後にのみ実行されるため、UIをブロックしません。一方、コンポーネントの関数本体内に直接記述されたコードは、レンダリング中に実行される(これによりレンダリングがブロックされる)ため、レンダリングが行われるたびに実行されます。主な違いは、useEffect を使用すると、コンポーネントがアンマウントされた際にクリーンアップ処理を実行できる点です。Q なぜ
useEffect のクリーンアップ関数は、再実行のたびに実行されるのですか?A これは React の設計上の保証であり、依存関係の変更によって引き起こされる「レースコンディション」を防ぐためです。例えば、
userIdが変更された場合、新しいリクエストが開始される前に古いリクエストがクリーンアップされ、新しいデータが古いデータを上書きすることが保証されます。もし新しいリクエストが古いリクエストを直接上書きしてしまうと、「古いリクエストが先に完了する → データが元に戻る」というバグが発生する可能性があります。Q 複数の
useEffect フックの実行順序はどのようになりますか?A コード内に記述されている順序で実行されます。したがって、「初期化」(データの読み込みなど)を処理する
useEffect フックを最初に配置し、「変更への対応」(localStorage への状態の同期など)を処理するフックを最後に配置する必要があります。1回のレンダリング中に、すべての useEffect フックが順番に実行されます。Q
useEffect と useLayoutEffect の違いは何ですか?A 実行されるタイミングが異なります。
useEffect は DOM が更新された後(ブラウザがレンダリングした後)に非同期で実行されるため、ユーザーにはちらつきが見られません。useLayoutEffectは、DOMが更新された後(ブラウザがレンダリングを行う前)に同期的に実行されるため、DOMを変更してからブラウザにレンダリングさせることができます。99%のケースでは useEffect を使用してください。useLayoutEffect は、「DOMのレイアウトに基づいて計算を行い、その後同期的にDOMを変更する必要がある」場合(要素の寸法を測定した後にその位置を調整する場合など)にのみ使用してください。📖 まとめ
useEffect(fn, deps)UIをブロックすることなく、レンダリング完了後に副作用を実行する- 空の依存関係
[]= マウント時に1回のみ実行される;空でない依存関係 = マウント時および依存関係が変更された際に実行される - クリーンアップ関数
return () => {}は、メモリリークを防ぐための鍵となります。タイマー、イベントリスナー、サブスクリプションはすべてクリーンアップする必要があります。 - 90%のケースでは
useEffectを使用し、DOMを測定する場合にのみuseLayoutEffectを使用してください。 - 複数の
useEffectフックは、記述された順序で実行されます
📝 練習問題
- 基本問題(難易度 ⭐):
PageTitleコンポーネントを作成してください。useEffectを使用して、コンポーネントがマウントされた際にページタイトルを「Welcome to My Website」に変更し、アンマウントされた際には元のタイトルに戻してください。 - 上級演習(難易度 ⭐⭐):
OnlineStatusコンポーネントを作成し、useEffectを使用してnavigator.onLineおよびonline/offlineからのイベントを監視し、ネットワークの状態をリアルタイムで表示するようにしてください。 - 課題(難易度:⭐⭐⭐):パラメータ
urlおよびintervalを受け取り、インターフェースを interval ミリ秒ごとに 1 回ポーリングし、{ data, isLoading, error }を返す、usePollingという名前のカスタムフックを作成してください。このフックは、ポーリングの一時停止と再開に対応している必要があります。