404 Not Found

404 Not Found


nginx

Vue 3 のコンポーネントのライフサイクル:8つのフックと onMounted/onUnmounted の実践的な活用

コンポーネントのライフサイクルとは、Vue コンポーネントが 生成から破棄 に至るまでの全プロセスを指します。具体的には、インスタンス化 → DOM へのマウント → データの更新 → アンマウントです。Vue では、特定のタイミングでコードを実行できる 8 つのライフサイクルフックが用意されています。

ライフサイクルを理解することは、「生き生きとした」コンポーネントを作成するための鍵となります。マウント時にデータを読み込み、更新時に副作用を実行し、アンマウント時にリソースをクリーンアップすることができます。

1. 学習内容


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.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>

ユーザー体験:

(3) 収益

ライフサイクルを追加した後:


3. ライフサイクル全体のフローチャート

100%
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) マウントの順序

100%
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

正しい順序

100%
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 onMountedonCreated の違いは何ですか?
A onCreated が呼び出される時点では、コンポーネントは作成されていますが、DOMはまだレンダリングされていません(そのため、DOMを操作することはできません)。onMounted が呼び出される時点では、DOMがマウントされています(そのため、DOMを操作することができます)。データの読み込みは、一般的に onMounted で行われます(SSR の問題を回避するため)。
Q ライフサイクルフックは非同期にできますか?
A はい。onMounted(async () => { await fetch(...) }) は有効な構文です。Vue は、非同期操作が完了するのを待たずに処理を続行します。
Q keep-alive における onActivatedonMounted の違いは何ですか?
A onMounted は(最初のマウント時に)1回だけトリガーされます。onActivatedは、ビューがキャッシュから復元されるたびにトリガーされます。タブの切り替え時には、onActivatedonDeactivatedがトリガーされます(onMountedonUnmountedはトリガーされません)。
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のみです。

📖 まとめ


📝 練習問題

(1) 基礎問題(難易度:⭐)

簡単な UserCard コンポーネントを実装します:

(2) 上級問題(難易度:⭐⭐)

タイマーコンポーネントを実装する:

(3) チャレンジ問題(難易度:⭐⭐⭐)

完全なタブシステムを実装する:

  1. TabContainer.vue:3つのタブ(ホーム/プロフィール/設定)
  2. 3つのタブコンテンツコンポーネント
  3. <keep-alive> キャッシュコンポーネントを使用する
  4. タブ出力を切り替える「Xが有効、Yが無効」
  5. onMounted を使用して、いずれかのタブにデータを読み込みます。
  6. 他の画面に切り替えた際、接続は(キープアライブのため)閉じられないため、元の画面に戻った際もデータが残っている
  7. onErrorCaptured を使用して、子コンポーネントのエラーを捕捉する
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%