Vue3 錯誤處理與除錯 – onErrorCaptured 完全指南
簡介
在大型 Vue3 應用程式中,錯誤不僅會影響使用者體驗,也可能導致資料遺失或功能中斷。因此,掌握 Vue 內建的錯誤捕獲機制是每位前端開發者的必備功課。onErrorCaptured 是 Vue3 中提供的 組件層級錯誤攔截 API,允許我們在子組件拋出錯誤時,於父層或祖層組件先行處理、記錄或轉譯錯誤,再決定是否讓錯誤繼續向上冒泡。
本篇文章將從概念、實作、常見陷阱到最佳實踐,一步步帶你在 Vue 3 Composition API 中善用 onErrorCaptured,打造更穩定、易除錯的應用。
核心概念
1. 為什麼需要 onErrorCaptured?
- 錯誤範圍控制:只捕獲特定子樹的錯誤,而不影響全局錯誤處理 (
app.config.errorHandler)。 - 錯誤轉譯:將技術性錯誤轉換成使用者友善的訊息或 UI。
- 資源釋放:在錯誤發生時,執行清理工作(例如取消訂閱、釋放計時器)。
注意:
onErrorCaptured只能在 setup 或 組件選項 API 中使用,且只能在 渲染期間 拋出的錯誤被捕獲(例如render、setup、watchEffect)。
2. 基本語法
import { onErrorCaptured } from 'vue'
export default {
setup() {
// 捕獲子組件錯誤
onErrorCaptured((err, instance, info) => {
console.error('子組件錯誤:', err)
// 回傳 true 可阻止錯誤繼續向上冒泡
return false // 讓錯誤繼續冒泡到更上層或全局處理器
})
}
}
參數說明
err:實際的Error物件。instance:拋出錯誤的子組件實例 (ComponentPublicInstance)。info:錯誤發生的上下文字串("render"、"setup"、"watchEffect"等)。
回傳值
false(預設) → 錯誤會繼續向上冒泡。true→ 錯誤被阻止,不會傳遞到更上層或全局錯誤處理器。
3. 與全局錯誤處理 (app.config.errorHandler) 的關係
| 層級 | 觸發時機 | 是否會觸發下一層 |
|---|---|---|
setup/render 中的 onErrorCaptured |
子組件錯誤 | 取決於回傳值 |
app.config.errorHandler |
尚未被 onErrorCaptured 阻止的錯誤 |
只會跑一次 |
實務建議:先在局部使用
onErrorCaptured針對特定功能做細部處理,再把未處理的錯誤交給全局errorHandler做最後的紀錄或上報。
4. 常見使用情境
| 情境 | 為什麼要用 onErrorCaptured |
|---|---|
| 表單驗證失敗的非同步請求 | 可在父層顯示統一的錯誤提示 |
| 第三方 UI 元件拋出渲染錯誤 | 父層可回退到備用 UI,避免整頁崩潰 |
| 多層嵌套的路由視圖 | 在根路由捕獲子路由錯誤,統一導向錯誤頁面 |
程式碼範例
下面提供 五個實用範例,從最簡單的錯誤捕獲到進階的錯誤轉譯與資源清理。
範例 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)
// 不阻止冒泡,讓全局 errorHandler 仍能收到
return false
})
</script>
// Child.vue
<template>
<div>{{ data.text }}</div>
</template>
<script setup>
import { ref } from 'vue'
const data = ref(null)
// 故意在 render 時拋出錯誤
if (!data.value) {
throw new Error('data 尚未初始化')
}
</script>
說明:
Parent.vue透過onErrorCaptured監聽子組件Child.vue的渲染錯誤,並把錯誤資訊寫入 console。
範例 2️⃣:阻止錯誤向上冒泡
// Dashboard.vue
<template>
<ErrorProneComponent />
<p v-if="errorMsg" class="error">{{ errorMsg }}</p>
</template>
<script setup>
import { ref, onErrorCaptured } from 'vue'
import ErrorProneComponent from './ErrorProneComponent.vue'
const errorMsg = ref('')
onErrorCaptured((err) => {
errorMsg.value = `發生錯誤:${err.message}`
// 返回 true 阻止錯誤繼續冒泡
return true
})
</script>
說明:當
ErrorProneComponent拋出錯誤時,Dashboard.vue直接在畫面上顯示錯誤訊息,且 不讓錯誤傳到全局,避免整個應用程式崩潰。
範例 3️⃣:錯誤轉譯成使用者友善訊息
// ApiFetcher.vue
<template>
<button @click="load">載入資料</button>
<div v-if="data">{{ data }}</div>
</template>
<script setup>
import { ref, onErrorCaptured } from 'vue'
const data = ref(null)
async function load() {
// 假設此 API 會拋出 network error
const res = await fetch('https://api.example.com/data')
if (!res.ok) throw new Error('Network response was not ok')
data.value = await res.json()
}
// 捕獲非同步錯誤
onErrorCaptured((err) => {
// 只處理我們關心的錯誤類型
if (err.message.includes('Network')) {
alert('無法連線至伺服器,請稍後再試。')
return true // 阻止錯誤冒泡
}
return false
})
</script>
說明:
onErrorCaptured會捕獲load()中的非同步錯誤,轉成 彈窗提醒,同時阻止錯誤繼續傳遞。
範例 4️⃣:在錯誤發生時清理資源
// TimerComponent.vue
<template>
<p>倒數:{{ count }}</p>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, onErrorCaptured } from 'vue'
const count = ref(10)
let timerId = null
onMounted(() => {
timerId = setInterval(() => {
count.value--
if (count.value === 5) {
// 故意拋出錯誤,模擬計時過程中的例外
throw new Error('計時器意外錯誤')
}
}, 1000)
})
// 捕獲錯誤後清除計時器
onErrorCaptured((err) => {
clearInterval(timerId)
console.error('計時器已被清除:', err.message)
// 仍讓錯誤冒泡,以便全局紀錄
return false
})
onBeforeUnmount(() => clearInterval(timerId))
</script>
說明:當倒數計時中拋出錯誤,
onErrorCaptured會立即 清除計時器,避免資源泄漏,同時把錯誤交給全局處理。
範例 5️⃣:在路由守衛中結合 onErrorCaptured
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import NotFound from '@/views/NotFound.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/:catchAll(.*)', component: NotFound }
]
})
// 全局路由錯誤處理
router.onError((error) => {
console.error('路由錯誤:', error)
})
export default router
// App.vue
<template>
<router-view />
</template>
<script setup>
import { onErrorCaptured } from 'vue'
import router from './router'
onErrorCaptured((err, instance, info) => {
// 若是路由懶載入失敗,導向 404
if (err.message.includes('Failed to fetch dynamically imported module')) {
router.replace('/404')
return true // 阻止錯誤冒泡
}
return false
})
</script>
說明:當懶載入的路由組件失敗(例如網路斷線),
App.vue會捕獲錯誤並 自動導向 404,提升使用者體驗。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
只在 setup 中使用 |
在普通的 methods 或 computed 中呼叫 onErrorCaptured 不會生效。 |
確保把 onErrorCaptured 放在 setup() 內或使用 Options API 的 errorCaptured 鉤子。 |
| 忘記回傳布林值 | 預設回傳 undefined 會被視為 false,錯誤會繼續冒泡。 |
明確 return true(阻止)或 return false(允許)。 |
| 捕獲非渲染錯誤 | onErrorCaptured 只會捕獲 渲染階段(render、setup、watchEffect)的錯誤,無法捕獲 async/await 之外的 Promise 錯誤。 |
針對 Promise 使用 try/catch 或全局 app.config.errorHandler。 |
| 過度阻止錯誤 | 把所有錯誤都 return true 會隱藏真正的程式錯誤,造成除錯困難。 |
僅對可預期的錯誤阻止,其他錯誤仍交給全局處理或開發者工具。 |
| 在同一層多次註冊 | 多次呼叫 onErrorCaptured 會產生多個攔截器,順序不易控制。 |
盡量在同一個 setup 中集中管理,或使用自訂 composable 包裝。 |
最佳實踐清單
- 先局部、後全局:先用
onErrorCaptured處理特定子樹的錯誤,再讓未處理的錯誤交給app.config.errorHandler。 - 只阻止可預期錯誤:例如 API 失敗、使用者輸入錯誤;對程式邏輯錯誤仍保持冒泡。
- 加入錯誤上報:在捕獲函式內呼叫自訂的
logError(error),將錯誤送至 Sentry、LogRocket 等服務。 - 結合 TypeScript:使用
Error的自訂型別(interface ApiError extends Error { code: number })提升錯誤辨識度。 - 保持 UI 回饋:捕獲錯誤後即時更新 UI(toast、dialog、inline message),避免使用者感到「卡住」或「無回應」。
實際應用場景
1. 表單提交失敗的集中處理
在大型表單中,每個欄位可能都有獨立的驗證元件。透過在表單父層使用 onErrorCaptured,可以 一次捕獲所有子元件的驗證例外,並顯示統一的錯誤訊息列。
2. 第三方圖表庫渲染錯誤
使用如 ECharts、Chart.js 時,若資料格式不正確會導致渲染階段拋錯。將圖表容器包在一個 ChartWrapper,在外層使用 onErrorCaptured 捕獲渲染錯誤,自動切換顯示「暫無資料」,避免整頁崩潰。
3. 多層路由懶載入失敗
在 SPA 中,常透過 defineAsyncComponent 懶載入路由組件。若網路斷線或資源不存在,會拋出錯誤。透過根組件的 onErrorCaptured,可以 偵測到此類錯誤並導向自訂的 404/500 頁面。
4. 跨域請求的統一錯誤 UI
在微前端架構裡,各子應用可能自行發送跨域請求。將所有子應用包在一個 MicroAppContainer,使用 onErrorCaptured 捕獲子應用的網路錯誤,統一顯示「服務暫時不可用」,提升整體使用者體驗。
總結
onErrorCaptured是 Vue3 組件層級的錯誤攔截 API,讓我們在子樹錯誤發生時即時處理、轉譯或清理資源。- 正確使用 回傳布林值、限制捕獲範圍,可以避免錯誤被過度隱藏,同時保持除錯的透明度。
- 結合全局
app.config.errorHandler、路由守衛或自訂上報服務,能形成 多層次、彈性且可維護 的錯誤處理機制。
透過本文的概念說明與實作範例,你應該已經能在日常開發中熟練使用 onErrorCaptured,讓 Vue3 應用在面對不可預期的例外時,仍能保持 穩定、友善且易於除錯。祝開發順利! 🚀