Vue 3 のコンポーネントのライフサイクル:8つのフックと onMounted/onUnmounted の実践的な活用
コンポーネントのライフサイクルとは、Vue コンポーネントが 生成から破棄 に至るまでの全プロセスを指します。具体的には、インスタンス化 → DOM へのマウント → データの更新 → アンマウントです。Vue では、特定のタイミングでコードを実行できる 8 つのライフサイクルフックが用意されています。
ライフサイクルを理解することは、「生き生きとした」コンポーネントを作成するための鍵となります。マウント時にデータを読み込み、更新時に副作用を実行し、アンマウント時にリソースをクリーンアップすることができます。
1. 学習内容
- コンポーネントのライフサイクル(生成から破棄まで)を示す完全なフローチャート
- 8つのライフサイクルフック(onBeforeMount、onMountedなど)
- Composition API の構文(onX 命名規則)
- 各フックの最適な活用例
- 親子コンポーネントのライフサイクルの実行順序
- キャッシュされたコンポーネントのキープアライブ(有効/無効)
- エラー処理 onErrorCaptured
2. データ読み込みコンポーネントにおける「ちらつき」の問題
(1) 課題:コンポーネントはレンダリングされたが、データはまだ読み込まれていない
アリスはユーザープロファイルコンポーネントを作成しました:
VUE
<!-- ❌ The "Flip" Version:Rendering Before Data Arrives -->
<template>
<div>
<h1>{{ user.name }}</h1>
<p>{{ user.email }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const user = ref({}) // Empty object,Display during rendering "undefined"
fetch('/api/user')
.then(res => res.json())
.then(data => user.value = data)
</script>
ユーザー体験:
- 0 ms:ページに「undefined undefined」と表示される(0.5秒)
- 500 ms:データが到着し、「Alicealice@example.com」と表示される
- 0.5秒間ちらつきが生じ、ユーザー体験が著しく損なわれる
プロダクトマネージャーのチャーリー:
「アリス、ユーザーには0.5秒間『undefined』と表示されてしまいます。読み込み中の状態を示す必要があります。データが届くまで『読み込み中...』と表示してください。」
(2) Vueのライフサイクルと「Loading」状態への対処法
VUE
<template>
<div>
<p v-if="loading">Loading...</p>
<div v-else>
<h1>{{ user.name }}</h1>
<p>{{ user.email }}</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const user = ref(null)
const loading = ref(true)
// ✅ Load data in onMounted... (DOM Ready, loading status will be displayed first)
onMounted(async () => {
const res = await fetch('/api/user')
user.value = await res.json()
loading.value = false
})
</script>
ユーザー体験:
- 0 ms:「読み込み中...」と表示する ✅
- 500 ms:データが到着し、「Alice alice@example.com」と表示される ✅
- ちらつきなし ✅
(3) 収益
ライフサイクルを追加した後:
- ちらつき問題:100% → 0
- ユーザー体験:読み込み状況が明確に表示される
- 拡張性:すべてのデータコンポーネントに同じパターンを適用できる
3. ライフサイクル全体のフローチャート
graph TB
A[Create a component instance] --> B[onBeforeCreate]
B --> C[setup Responsive]
C --> D[onCreated]
D --> E[Template Compilation]
E --> F[onBeforeMount]
F --> G[Mount to DOM]
G --> H[onMounted]
H --> I{Data Update?}
I -->|Yes| J[onBeforeUpdate]
J --> K[Render Again]
K --> L[onUpdated]
L --> I
I -->|No| M{Component Uninstall?}
M -->|Yes| N[onBeforeUnmount]
N --> O[Unmount]
O --> P[onUnmounted]
style H fill:#42b883,color:#fff
style L fill:#42b883,color:#fff
style P fill:#42b883,color:#fff
4. 8つのライフサイクルフックの詳細な解説
(1) 8つの主要なフックのクイックリファレンス
| フック | トリガー条件 | 一般的なシナリオ | 頻度 |
|---|---|---|---|
onBeforeMount |
部品実装前 | データの準備 | ⭐ |
onMounted |
コンポーネントのマウント後 | データの読み込み、DOM操作 | ⭐⭐⭐⭐⭐ |
onBeforeUpdate |
データ更新前 | パフォーマンスの最適化 | ⭐⭐ |
onUpdated |
データが更新された後 | DOMの再レンダリング後の処理 | ⭐⭐⭐ |
onBeforeUnmount |
コンポーネントをアンロードする前に | タイマーをクリア | ⭐⭐⭐ |
onUnmounted |
コンポーネントのアンインストール後 | 最終的なクリーンアップ | ⭐⭐ |
onErrorCaptured |
子コンポーネントのエラーを検出 | エラー境界 | ⭐⭐⭐ |
onActivated |
キープアライブ有効 | キャッシュコンポーネントが復元されました | ⭐⭐ |
onDeactivated |
キープアライブ無効 | キャッシュ機能無効 | ⭐⭐ |
(2) onMounted:最もよく使われる
VUE
<script setup>
import { ref, onMounted } from 'vue'
const user = ref(null)
onMounted(async () => {
// 1. Loading initial data
const res = await fetch('/api/user')
user.value = await res.json()
// 2. DOM Operations(Realistic operation DOM)
document.title = `${user.value.name} - Dashboard`
// 3. Register a global event listener
window.addEventListener('resize', handleResize)
// 4. Set a Timer
const timer = setInterval(() => {
console.log('Tick')
}, 1000)
// ❌ Note: onUnmounted must clean up timer and listener
})
</script>
(3) onUnmounted:リソースのクリーンアップ
VUE
<script setup>
import { onMounted, onUnmounted } from 'vue'
let timer = null
onMounted(() => {
timer = setInterval(() => console.log('Tick'), 1000)
window.addEventListener('resize', handleResize)
})
// ✅ Cleanup:Avoiding Memory Leaks
onUnmounted(() => {
clearInterval(timer)
window.removeEventListener('resize', handleResize)
})
</script>
(4) onErrorCaptured: エラー境界
VUE
<!-- ErrorBoundary.vue Parent Component -->
<script setup>
import { onErrorCaptured, ref } from 'vue'
const error = ref(null)
onErrorCaptured((err, instance, info) => {
console.error('Caught error:', err)
console.log('Component:', instance)
console.log('Info:', info)
error.value = err.message
return false // Prevent errors from propagating upward
})
</script>
<template>
<div>
<p v-if="error" class="error">Error: {{ error }}</p>
<slot v-else />
</div>
</template>
VUE
<!-- App.vue Usage -->
<ErrorBoundary>
<UserProfile :user-id="123" /> <!-- If an error occurs, will be caught by ErrorBoundary -->
</ErrorBoundary>
(5) 5つの主なユースケース
| シナリオ | フック | コード |
|---|---|---|
| 初期データの読み込み中 | onMounted |
await fetch(...) |
| タイマー設定 | onMounted + onUnmounted |
setInterval + clearInterval |
| 世界的な出来事に耳を傾ける | ||
| DOM操作 | onMounted |
document.querySelector(...) |
| サードパーティ製ライブラリの統合 | onMounted + onUnmounted |
new Chart(...) + chart.destroy() |
5. 親コンポーネントと子コンポーネントのライフサイクルの実行順序
(1) マウントの順序
sequenceDiagram
participant P as Parent
participant C as Child
P->>P: 1. Parent onBeforeMount
P->>P: 2. Parent onMounted
C->>C: 3. Child onBeforeMount
C->>C: 4. Child onMounted
Note over P,C: ❌ That's wrong: Parent mounted should come after Child
正しい順序:
sequenceDiagram
participant P as Parent
participant C as Child
P->>P: 1. Parent onBeforeMount
C->>C: 2. Child onBeforeMount
C->>C: 3. Child onMounted
P->>P: 4. Parent onMounted
Note over P,C: ✅ Mount only after all child components, including the parent component, have finished mounting
(2) 実行順序の完了(ネストされたコンポーネント)
TEXT
1. Parent onBeforeCreate
2. Parent setup
3. Parent onCreated
4. Parent onBeforeMount
5. Child onBeforeCreate
6. Child setup
7. Child onCreated
8. Child onBeforeMount
9. Child onMounted
10. Parent onMounted
(3) アンインストール手順(逆順)
TEXT
1. Parent onBeforeUnmount
2. Child onBeforeUnmount
3. Child onUnmounted
4. Parent onUnmounted
6. キープアライブ・キャッシング・コンポーネント
(1) キープアライブとは何ですか?
<keep-alive> コンポーネントのインスタンスをキャッシュし、繰り返し作成・破棄されるのを防ぎます。タブの切り替えやルートの切り替えが行われる場面でよく使用されます。
VUE
<!-- Parent Component -->
<template>
<button v-for="tab in tabs" :key="tab" @click="currentTab = tab">
{{ tab }}
</button>
<!-- keep-alive Package:Switch Tab Do not destroy the component -->
<keep-alive>
<component :is="currentTabComponent" />
</keep-alive>
</template>
(2) 2つの新しいフック
VUE
<!-- Child component: Cached by keep-alive -->
<script setup>
import { onActivated, onDeactivated } from 'vue'
// When the component is activated(Restore from Cache)
onActivated(() => {
console.log('Component activated')
// Reload Data、Restore scroll position, etc.
})
// When a component is disabled(Cut but keep in cache)
onDeactivated(() => {
console.log('Component deactivated')
// Save Status、Pause the timer, etc.
})
</script>
(3) 5つの主なユースケース
| シナリオ | 方法 |
|---|---|
| タブの切り替え | <keep-alive> タブの内容を折り返す |
| ルート切り替え | <keep-alive> パッケージ <router-view> |
| ポップアップキャッシュ | <keep-alive> パッケージポップアップ |
| リストページ | キャッシュされた読み込み済みリスト |
| フォームページ | 送信されていないフォームをキャッシュ |
7. 完全な例:ユーザープロファイルコンポーネント
(1) ▶ サンプル:. onMounted + onUnmounted(データの読み込み + クリーンアップ)
VUE
<template>
<div>
<p v-if="loading">Loading...</p>
<div v-else-if="user">
<h1>{{ user.name }}</h1>
<p>{{ user.email }}</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const props = defineProps({ userId: { type: Number, required: true } })
const user = ref(null)
const loading = ref(true)
let timer = null
onMounted(async () => {
// 1. Loading data
const res = await fetch(`/api/users/${props.userId}`)
user.value = await res.json()
loading.value = false
// 2. Set a Timer (Every 30s Refresh)
timer = setInterval(async () => {
const res = await fetch(`/api/users/${props.userId}`)
user.value = await res.json()
}, 30000)
// 3. Monitoring Window Size Changes
window.addEventListener('resize', () => {
console.log('Window resized')
})
})
onUnmounted(() => {
// ✅ Cleanup:Avoiding Memory Leaks
clearInterval(timer)
window.removeEventListener('resize', () => {})
})
</script>
(2) ▶ サンプル:. 8つのフックのクイックリファレンス
VUE
<script setup>
import {
onBeforeMount, onMounted,
onBeforeUpdate, onUpdated,
onBeforeUnmount, onUnmounted,
onErrorCaptured, onActivated, onDeactivated
} from 'vue'
// 1. Before Mounting
onBeforeMount(() => {
console.log('1. Preparing to mount')
})
// 2. After mounting(Most Commonly Used)
onMounted(() => {
console.log('2. Mounted,Loading data')
})
// 3. Before the update
onBeforeUpdate(() => {
console.log('3. Coming Soon')
})
// 4. After the update
onUpdated(() => {
console.log('4. Updated')
})
// 5. Before Uninstalling
onBeforeUnmount(() => {
console.log('5. Preparing to uninstall,Free Up Resources')
})
// 6. After uninstallation
onUnmounted(() => {
console.log('6. Uninstalled')
})
// 7. Error Handling
onErrorCaptured((err) => {
console.error('7. Child Component Error:', err)
return false
})
// 8. keep-alive Activate
onActivated(() => {
console.log('8. keep-alive Activate')
})
// 9. keep-alive Disable
onDeactivated(() => {
console.log('9. keep-alive Disable')
})
</script>
(3) ▶ サンプル:. 親コンポーネントと子コンポーネントのライフサイクルの順序
VUE
<!-- Parent.vue -->
<template>
<Child :data="parentData" />
</template>
<script setup>
import { onMounted } from 'vue'
onMounted(() => console.log('4. Parent mounted'))
</script>
<!-- Child.vue -->
<template>
<p>{{ data }}</p>
</template>
<script setup>
import { onMounted } from 'vue'
onMounted(() => console.log('3. Child mounted'))
</script>
<!-- Order of Console Output:
1. Child created
2. Child mounted
3. Parent mounted
(Because the parent component must wait until the child component has finished mounting before it can mount itself) -->
(4) ▶ サンプル:. onErrorCaptured エラー境界
VUE
<!-- ErrorBoundary.vue -->
<script setup>
import { onErrorCaptured, ref } from 'vue'
const error = ref(null)
onErrorCaptured((err, instance, info) => {
console.error('Caught error:', err)
console.log('Component:', instance?.$options.name)
console.log('Info:', info) // 'render' / 'watch' / 'lifecycle hook'
error.value = err.message
return false // Prevent upward transmission
})
</script>
<template>
<div v-if="error" class="error-banner">
⚠️ Error: {{ error }}
</div>
<slot v-else />
</template>
(5) ▶ サンプル:. キープアライブ・キャッシング・コンポーネント
VUE
<!-- TabContainer.vue -->
<template>
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab"
:class="{ active: currentTab === tab }"
@click="currentTab = tab"
>
{{ tab }}
</button>
<!-- keep-alive Cache: Preserve component state when switching away -->
<keep-alive>
<component :is="currentTabComponent" />
</keep-alive>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import Home from './tabs/Home.vue'
import Profile from './tabs/Profile.vue'
import Settings from './tabs/Settings.vue'
const tabs = ['Home', 'Profile', 'Settings']
const currentTab = ref('Home')
const currentTabComponent = computed(() => {
return { Home, Profile, Settings }[currentTab.value]
})
</script>
(6) ▶ サンプル:. よくある5つの間違いに関するクイックリファレンス
| エラー | 症状 | 解決策 |
|---|---|---|
setup の最上位で document を使用 |
サーバーエラー | onMounted を使用 |
| タイマーのクリーンアップが行われていない | メモリリーク | onUnmounted でクリア |
| onMounted 内でプロパティを変更する | Vue の警告 | 代わりに emit を使用する |
| 非同期コンポーネントはエラーを処理しない | サイレント失敗 | onErrorCaptured |
| 親子マウントの順序が間違っている | デバッグの混乱 | 「親は子と同じ」という原則の理解 |
❓ よくある質問
Q
onMounted と onCreated の違いは何ですか?A
onCreated が呼び出される時点では、コンポーネントは作成されていますが、DOMはまだレンダリングされていません(そのため、DOMを操作することはできません)。onMounted が呼び出される時点では、DOMがマウントされています(そのため、DOMを操作することができます)。データの読み込みは、一般的に onMounted で行われます(SSR の問題を回避するため)。Q ライフサイクルフックは非同期にできますか?
A はい。
onMounted(async () => { await fetch(...) }) は有効な構文です。Vue は、非同期操作が完了するのを待たずに処理を続行します。Q
keep-alive における onActivated と onMounted の違いは何ですか?A
onMounted は(最初のマウント時に)1回だけトリガーされます。onActivatedは、ビューがキャッシュから復元されるたびにトリガーされます。タブの切り替え時には、onActivatedとonDeactivatedがトリガーされます(onMountedとonUnmountedはトリガーされません)。Q
onErrorCaptured はどのような種類のエラーを捕捉できますか?A 以下の 3 種類のエラーを捕捉できます:(1) 子コンポーネントのレンダリングエラー、(2) 子コンポーネントのライフサイクルフックエラー、(3) 子コンポーネントのウォッチコールバックエラー。ただし、自身で発生したエラーや非同期エラーは捕捉できません。
Q 親コンポーネントは子コンポーネントのライフサイクルイベントを監視できますか?
A はい。@hook を使用して監視することができます:
@hook:mounted="handleChildMounted"。ただし、ライフサイクルイベントを直接監視するよりも、props や emit を使った通信を行うことをお勧めします。Q Vue 2とVue 3のライフサイクルフックの違いは何ですか?
A Vue 3では、フックの名前が
onMountedに変更されました(Vue 2ではmountedでした)。また、onErrorCapturedおよびonActivated/onDeactivatedが追加されました。Vue 2のフックはすべてVue 3でも利用可能です(下位互換性は維持されています)。Q SSR(サーバーサイドレンダリング)の際、ライフサイクルはどのように動作しますか?
A サーバー側では(DOMが存在しないため)、onBeforeMount、onMounted、onBeforeUpdate、onUpdatedはトリガーされません。トリガーされるのは、onBeforeCreate、onCreated、onBeforeUnmount、onUnmounted、onErrorCapturedのみです。
📖 まとめ
- 8つのライフサイクルフック:onBeforeMount / onMounted / onBeforeUpdate / onUpdated / onBeforeUnmount / onUnmounted / onErrorCaptured / onActivated
- onMounted(最も一般的に使用される):DOMの操作、データの読み込み、イベントの登録
- onUnmounted:必須のクリーンアップ:タイマー、イベントリスナー、サードパーティ製ライブラリ
- 親子順序:親の beforeMount → 子の beforeMount → 子の mounted → 親の mounted
- キャッシュされたコンポーネントに対するキープアライブ + onActivated/onDeactivated
- onErrorCaptured: エラー境界を実装する
- サーバーサイドレンダリング中は、一部のフックがトリガーされない
📝 練習問題
(1) 基礎問題(難易度:⭐)
簡単な UserCard コンポーネントを実装します:
- onMounted:ユーザーデータを読み込む(1秒の遅延をシミュレートする)
- 「読み込み中」を表示 → ユーザー情報を表示
- アンマウント時のログ「コンポーネントが破棄されました」
(2) 上級問題(難易度:⭐⭐)
タイマーコンポーネントを実装する:
- onMounted: タイマーを開始する(1秒ごとに+1)
- 現在の秒を表示する
- onUnmounted: タイマーをクリーンアップする
- 一時停止/再開用のボタンを追加する(状態の保存には
refを使用する)
(3) チャレンジ問題(難易度:⭐⭐⭐)
完全なタブシステムを実装する:
- TabContainer.vue:3つのタブ(ホーム/プロフィール/設定)
- 3つのタブコンテンツコンポーネント
<keep-alive>キャッシュコンポーネントを使用する- タブ出力を切り替える「Xが有効、Yが無効」
onMountedを使用して、いずれかのタブにデータを読み込みます。- 他の画面に切り替えた際、接続は(キープアライブのため)閉じられないため、元の画面に戻った際もデータが残っている
onErrorCapturedを使用して、子コンポーネントのエラーを捕捉する



