本文 AI 產出,尚未審核

Vue3 生命週期 – onErrorCaptured 完全指南


簡介

在大型單頁應用程式中,錯誤不只會讓功能中斷,更可能洩漏敏感資訊、破壞使用者體驗。Vue 3 為了讓開發者能在 組件層級 更精細地控制錯誤處理,提供了 onErrorCaptured 生命週期鉤子。它能夠 捕獲子組件拋出的錯誤,並決定錯誤是否要向上冒泡,從而避免錯誤「傳染」到整個應用。

本篇文章將從概念說明、實作範例、常見陷阱到最佳實踐,完整呈現 onErrorCaptured 的使用方式,讓你在開發 Vue 3 應用時,能以安全、可控的方式處理例外情況。


核心概念

1. 為什麼需要 onErrorCaptured

  • 局部化錯誤處理:在 Options API 中,errorCaptured 只能在父層組件捕獲子組件錯誤;Composition API 的 onErrorCaptured 同樣提供這個能力,同時支援在 setup 中直接使用。
  • 錯誤冒泡控制:回傳 false 可阻止錯誤向更上層傳遞,類似瀏覽器的事件冒泡機制。
  • 與全局錯誤處理的補完app.config.errorHandler 處理全局錯誤;onErrorCaptured 則是更細粒度的局部捕獲,兩者可以同時存在,形成多層防護。

2. 基本語法

import { onErrorCaptured } from 'vue'

export default {
  setup() {
    onErrorCaptured((err, instance, info) => {
      // err: 捕獲到的 Error 物件
      // instance: 發生錯誤的子組件實例
      // info: 錯誤發生的來源字串 (e.g., "render", "watcher", "event handler")
      console.error('捕獲錯誤:', err, info)
      // 回傳 false 可阻止錯誤冒泡
      return false
    })
  }
}
  • 參數說明

    • err: 真正的 Error 例外。
    • instance: 發生錯誤的子組件 Vue 實例(可用來取得組件名稱或狀態)。
    • info: 錯誤發生的上下文,常見值有 "render""watcher""event handler"
  • 回傳值

    • true(預設) → 錯誤會繼續向上冒泡至更高層的 onErrorCaptured 或全局錯誤處理器。
    • false → 錯誤不再向上冒泡,僅在當前層級處理。

3. 與 Options API 的對照

API 使用方式 何時呼叫
errorCaptured (Options) errorCaptured(err, vm, info) { … } 組件建立階段,即可使用
onErrorCaptured (Composition) onErrorCaptured((err, instance, info) => { … }) 必須在 setup() 中呼叫

Tip:如果你同時使用 Options API 與 Composition API,兩者的錯誤捕獲會依照宣告順序依次執行,最後仍會交給全局錯誤處理器。

4. 錯誤冒泡示意圖

RootComponent
 ├─ ParentA (onErrorCaptured)
 │    └─ ChildA1 (throws)
 └─ ParentB (onErrorCaptured)
      └─ ChildB1 (throws)
  • ChildA1 拋出錯誤 → ParentA 捕獲 → 若 ParentA 回傳 false,錯誤不會傳到 RootComponent
  • ChildB1 拋出錯誤 → ParentB 捕獲 → 若 ParentB 回傳 true,錯誤會繼續傳到 RootComponent,最終交給 app.config.errorHandler

程式碼範例

範例 1️⃣ 基本捕獲與阻止冒泡

// Parent.vue
<template>
  <Child />
</template>

<script setup>
import { onErrorCaptured } from 'vue'
import Child from './Child.vue'

onErrorCaptured((err, instance, info) => {
  console.warn('父組件捕獲錯誤:', err.message, '| 來源:', info)
  // 阻止錯誤冒泡,讓全局 errorHandler 不會被觸發
  return false
})
</script>
// Child.vue
<template>
  <button @click="triggerError">觸發錯誤</button>
</template>

<script setup>
function triggerError() {
  // 故意拋出錯誤
  throw new Error('Child component error')
}
</script>

說明:點擊按鈕時,錯誤會被 Parent.vue 捕獲並在 console 中顯示,且不會向上傳遞到根組件或全局處理器。


範例 2️⃣ 結合全局錯誤處理器

// main.js
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 全局錯誤處理
app.config.errorHandler = (err, vm, info) => {
  console.error('全局錯誤處理:', err.message, '| 組件:', vm?.$options?.name, '| 來源:', info)
  // 可在此上報至監控平台
}

app.mount('#app')
// GrandParent.vue
<template>
  <Parent />
</template>

<script setup>
import { onErrorCaptured } from 'vue'
import Parent from './Parent.vue'

onErrorCaptured((err) => {
  console.log('祖父組件收到錯誤,決定傳遞')
  // 返回 true,讓錯誤繼續冒泡到全局處理器
  return true
})
</script>

說明:若 Parent.vue 在範例 1 中改為 return true,錯誤會依序傳到 GrandParent.vue,最後觸發 app.config.errorHandler,在此可將錯誤上報至 Sentry、LogRocket 等服務。


範例 3️⃣ 捕獲異步錯誤(Promise)

// AsyncChild.vue
<template>
  <button @click="loadData">載入資料</button>
</template>

<script setup>
import { ref } from 'vue'

const data = ref(null)

async function loadData() {
  // 假設此 API 會回傳 500 錯誤,拋出例外
  const response = await fetch('https://api.example.com/invalid')
  if (!response.ok) {
    throw new Error('API 請求失敗')
  }
  data.value = await response.json()
}
</script>
// AsyncParent.vue
<template>
  <AsyncChild />
</template>

<script setup>
import { onErrorCaptured } from 'vue'
import AsyncChild from './AsyncChild.vue'

onErrorCaptured((err, instance, info) => {
  console.error('捕獲到非同步錯誤:', err.message, '| 來源:', info)
  // 仍回傳 false,避免全局處理器收到相同錯誤
  return false
})
</script>

說明:即使錯誤發生在 async/await 中,onErrorCaptured 仍能捕獲。Vue 會把未被 try/catch 包住的 Promise 拒絕視為渲染錯誤,傳遞給錯誤鉤子。


範例 4️⃣ 多層嵌套的錯誤過濾

// FilteredParent.vue
<template>
  <ChildA />
  <ChildB />
</template>

<script setup>
import { onErrorCaptured } from 'vue'
import ChildA from './ChildA.vue'
import ChildB from './ChildB.vue'

onErrorCaptured((err, instance, info) => {
  // 只過濾 ChildA 的錯誤
  if (instance?.type?.name === 'ChildA') {
    console.warn('過濾 ChildA 的錯誤:', err.message)
    return false   // 阻止冒泡
  }
  // 其他錯誤交給上層或全局處理
  return true
})
</script>

說明:透過 instance.type.name 可以辨識是哪個子組件拋出的錯誤,實作錯誤過濾策略,讓不同子組件有不同的錯誤處理方式。


範例 5️⃣ 與 errorCaptured 混用(Options API)

// MixedComponent.vue
<template>
  <slot />
</template>

<script>
export default {
  // Options API 版的錯誤捕獲
  errorCaptured(err, vm, info) {
    console.log('[Options] 捕獲錯誤:', err.message)
    // 仍允許冒泡
    return true
  },
  setup() {
    // Composition API 版的捕獲
    const { onErrorCaptured } = require('vue')
    onErrorCaptured((err) => {
      console.log('[Composition] 捕獲錯誤:', err.message)
      // 停止冒泡
      return false
    })
  }
}
</script>

說明:同一個組件同時使用兩種 API 時,Composition API 的 onErrorCaptured 會在 Options API 之後執行。若兩者回傳的值不一致,最後的返回值(Composition)會決定錯誤是否冒泡。


常見陷阱與最佳實踐

陷阱 說明 解決方案
忘記在 setup 中呼叫 onErrorCaptured 必須在 setup() 裡執行,否則不會註冊。 確保所有 Composition API 鉤子放在 setup 中。
回傳 undefined 若回傳值不是 truefalse,Vue 視為 true(繼續冒泡)。 明確 return false 以阻止冒泡,或 return true 表示允許。
捕獲同步錯誤但忽略異步 onErrorCaptured 只能捕獲未被 try/catch 包住的錯誤,async 函式內的 catch 會自行處理。 只在需要全局捕獲未處理異步錯誤時使用,仍建議在 async 中自行 try/catch
在子組件內部使用 throw 失去堆疊資訊 直接 throw new Error 會失去 Vue 處理過程的額外資訊。 使用 errorCaptured/onErrorCaptured 內部的 info 參數來補足上下文。
過度阻止錯誤冒泡 若所有子組件錯誤都被阻止,根本無法在全局層面監控問題。 僅在確定可以自行處理的情況下返回 false,其餘情況保持 true

最佳實踐

  1. 分層處理:在 UI 相關的父組件捕獲子組件錯誤,顯示友善訊息;在根組件或全局處理器上報錯誤至監控平台。
  2. 錯誤分類:利用 info(render、watcher、event handler)或自訂錯誤類別,決定不同的處理策略。
  3. 保持可讀性:在 onErrorCaptured 中盡量只做 記錄決策,避免執行過於複雜的邏輯,讓錯誤路徑清晰。
  4. 搭配 TypeScript:若使用 TS,請為 err 宣告類型 Error,並在回傳值前加上 /** @returns {boolean} 是否繼續冒泡 */,提升 IDE 提示。
  5. 測試錯誤流:使用 Jest 或 Vitest 撰寫單元測試,模擬子組件拋錯,驗證父組件的 onErrorCaptured 是否正確阻止或傳遞。

實際應用場景

場景 為何需要 onErrorCaptured 處理方式
表單驗證錯誤 子表單元件驗證失敗時拋出錯誤,父表單需要聚合錯誤訊息。 父表單使用 onErrorCaptured 收集錯誤,顯示彈窗或錯誤清單。
第三方元件失效 第三方 UI 元件(如圖表)在資料異常時會拋出例外,若不捕獲會導致整頁崩潰。 在包裹第三方元件的容器組件中使用 onErrorCaptured,顯示備用 UI(如「載入失敗」訊息)。
非同步資料請求 多個子組件同時發送 API 請求,任一失敗都不應影響其他子組件。 每個子組件自行拋錯,父層 onErrorCaptured 捕獲後只記錄,並允許其他子組件正常運作。
權限檢查失敗 某些子組件在渲染前需要檢查使用者權限,若無權限直接拋錯。 父層捕獲後導向登入頁或顯示「無權限」訊息,避免整個頁面白屏。
微前端/子應用整合 主應用載入多個子應用,子應用內部錯誤不應影響主應用。 主應用在每個子應用的容器上設定 onErrorCaptured,將錯誤限制在子應用範圍內。

總結

  • onErrorCaptured 是 Vue 3 Composition API 中的錯誤捕獲鉤子,提供 局部可控 的錯誤處理能力。
  • 透過回傳 false 可以 阻止錯誤冒泡,回傳 true(或不回傳)則讓錯誤繼續向上層或全局錯誤處理器傳遞。
  • 配合 app.config.errorHandler、監控平台與適當的 UI 回饋,可打造 彈性且安全 的錯誤處理機制。
  • 使用時要注意 setup 中註冊明確回傳布林值,以及 避免過度阻止冒泡,以免錯誤被「隱藏」而失去可觀測性。

掌握 onErrorCaptured 後,你將能在 Vue 3 應用中更細緻地管理例外情況,提升使用者體驗與系統穩定性。祝你開發順利,錯誤皆能被優雅捕獲! 🚀