Vue3 – 錯誤處理與除錯:全域錯誤邏輯
簡介
在單頁應用程式 (SPA) 中,錯誤不會像傳統多頁網站那樣直接跳出瀏覽器的錯誤頁面,而是會在 JavaScript 執行階段靜默發生。若沒有適當的錯誤捕捉與回報機制,使用者可能會看到「畫面卡住」或「功能失效」的情況,開發團隊也難以追蹤問題根源。
全域錯誤處理(Global Error Handling)正是為了在 Vue3 應用的最外層集中捕捉未處理的例外、Promise 錯誤或生命週期錯誤,進而統一記錄、顯示友善訊息,甚至自動上報給後端監控系統。
本篇文章將帶你從概念到實作,完整掌握 Vue3 中的全域錯誤邏輯,讓你的應用更穩定、更易除錯。
核心概念
1. Vue3 全域錯誤捕捉 API
Vue3 提供了兩個主要的全域錯誤捕捉鉤子:
| API | 說明 | 觸發時機 |
|---|---|---|
app.config.errorHandler |
捕捉 同步 錯誤(如 render、setup、生命週期) |
任何 component 內部未被 try/catch 包住的例外 |
app.config.warnHandler |
捕捉 Vue 警告(如 prop type 不符) | Vue 在開發環境發出警告時 |
這兩個設定在應用程式啟動時一次性註冊,之後所有子組件都會自動受惠。
2. 全域 Promise 錯誤(未捕捉的 rejected)
瀏覽器提供 window.onunhandledrejection 事件,可捕捉未被 .catch() 處理的 Promise 錯誤。將它與 Vue 的 errorHandler 結合,可做到 全域錯誤一網打盡。
3. 錯誤資訊的結構化
為了讓錯誤上報更有價值,建議將錯誤資訊整理為以下格式:
interface GlobalErrorPayload {
message: string; // 錯誤訊息
stack?: string; // 堆疊追蹤(若有)
component?: string; // 發生錯誤的 component 名稱
propsData?: Record<string, any>; // 當時傳入的 props
url: string; // 當前路由
timestamp: number; // 發生時間
}
4. 錯誤回報策略
- 本地顯示:使用者可見的 toast / dialog,提供友善提示。
- 遠端上報:將錯誤 payload POST 給錯誤監控服務(如 Sentry、BugSnag、或自行建置的 API)。
- 本地儲存:在無網路時先寫入
IndexedDB,待網路恢復再上傳。
程式碼範例
下面的範例展示了從 建立 Vue 應用、註冊全域錯誤處理、到 整合 Promise 錯誤與遠端上報 的完整流程。每段程式碼皆附上說明註解。
1️⃣ 建立 Vue3 應用與全域 errorHandler
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { reportError } from './utils/errorReporter' // 之後會實作
const app = createApp(App)
// ★ 設定全域 errorHandler
app.config.errorHandler = (err, vm, info) => {
// 1. 取得錯誤基本資訊
const payload = {
message: err.message,
stack: err.stack,
component: vm?.$options?.name || 'anonymous',
propsData: vm?.$props,
url: router.currentRoute.value.fullPath,
timestamp: Date.now(),
info, // Vue 提供的錯誤類型描述
}
// 2. 顯示友善訊息(使用 UI 框架的 toast)
vm?.$toast?.error('發生未預期的錯誤,已自動回報給開發團隊')
// 3. 上報至遠端(非同步,不阻塞 UI)
reportError(payload).catch(console.error) // 防止上報失敗拋出二次錯誤
}
// ★ 設定全域 warnHandler(可選)
app.config.warnHandler = (msg, vm, trace) => {
console.warn(`[Vue warn] ${msg}\nTrace: ${trace}`)
}
app.use(router).mount('#app')
重點:
errorHandler內部絕不應拋出錯誤,否則會形成遞迴。若上報失敗,只需要在catch中記錄即可。
2️⃣ 捕捉未處理的 Promise 錯誤
// errorGlobal.js(可在 main.js 之前 import)
window.addEventListener('unhandledrejection', event => {
const err = event.reason instanceof Error ? event.reason : new Error(String(event.reason))
const payload = {
message: err.message,
stack: err.stack,
component: 'global-promise',
url: location.href,
timestamp: Date.now(),
info: 'unhandledrejection',
}
// 同樣使用 reportError,上報遠端
reportError(payload).catch(console.error)
// 防止瀏覽器默認的 console 警告(可依需求保留)
event.preventDefault()
})
提示:在開發環境可以先
console.error(err)觀察,再決定是否上報。
3️⃣ 建立錯誤上報工具(簡易版)
// utils/errorReporter.js
export async function reportError(payload) {
// 若是離線狀態,先寫入 localForage(IndexedDB 包裝)
if (!navigator.onLine) {
const { setItem } = await import('localforage')
const queue = (await setItem('errorQueue', [])) || []
queue.push(payload)
await setItem('errorQueue', queue)
return
}
// 正式上報
const response = await fetch('/api/error-report', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
if (!response.ok) throw new Error('Error report failed')
}
說明:此範例示範了 離線緩衝、JSON 序列化 與 錯誤回傳檢查,讓上報更具彈性。
4️⃣ 在組件內使用 try/catch + 手動上報(補強)
<template>
<button @click="loadData">載入資料</button>
</template>
<script setup>
import { ref } from 'vue'
import { reportError } from '@/utils/errorReporter'
const data = ref(null)
async function loadData() {
try {
const res = await fetch('/api/data')
if (!res.ok) throw new Error('Network response was not ok')
data.value = await res.json()
} catch (err) {
// 手動上報,讓錯誤訊息更具上下文
await reportError({
message: err.message,
stack: err.stack,
component: 'DataFetcher',
url: location.href,
timestamp: Date.now(),
info: 'loadData() failed',
})
// 顯示 UI 提示
alert('資料載入失敗,請稍後再試')
}
}
</script>
技巧:即使已設定全域
errorHandler,在關鍵流程(如 API 呼叫)仍建議自行捕捉,加入業務層面的描述(info欄位),方便日後排查。
5️⃣ 整合 Sentry(第三方監控)作為遠端上報
// plugins/sentry.js
import * as Sentry from '@sentry/vue'
import { BrowserTracing } from '@sentry/tracing'
import { createApp } from 'vue'
export function initSentry(app) {
Sentry.init({
app,
dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0',
integrations: [new BrowserTracing()],
// 設定自訂的全域錯誤處理,讓 Sentry 捕捉到 Vue 的 errorHandler
vueOptions: {
errorHandler(err, vm, info) {
// 讓 Sentry 先收集
Sentry.captureException(err)
// 再交給我們自己的 handler(如上面的 app.config.errorHandler)
// 這裡可以直接呼叫 app.config.errorHandler
if (app.config.errorHandler) {
app.config.errorHandler(err, vm, info)
}
},
},
tracesSampleRate: 1.0,
})
}
// main.js 中使用
import { createApp } from 'vue'
import App from './App.vue'
import { initSentry } from '@/plugins/sentry'
const app = createApp(App)
initSentry(app) // 先掛載 Sentry
app.mount('#app')
優點:Sentry 自帶豐富的 UI 與聚合功能,開發者只需要在
errorHandler中呼叫Sentry.captureException即可。
常見陷阱與最佳實踐
| 陷阱 | 為什麼會發生 | 解法 |
|---|---|---|
| 錯誤處理本身拋出例外 | 在 errorHandler 內部再次呼叫會產生遞迴,最終導致瀏覽器崩潰。 |
永遠在 errorHandler 中使用 try/catch,或把上報邏輯抽成不會拋錯的 Promise。 |
| Promise 錯誤被 swallow | async 函式內未使用 .catch(),錯誤會直接走 unhandledrejection,但若在 errorHandler 中已處理,仍可能遺漏。 |
同時設置 window.onunhandledrejection,確保任何未捕捉的 rejected 都會被捕獲。 |
| 上報延遲造成使用者體驗卡頓 | 若直接在 UI 執行緒等待 fetch 完成,會阻塞畫面渲染。 |
使用非同步 fire‑and‑forget(不 await)或在 requestIdleCallback 中上報。 |
| 開發環境過度噪音 | 全域警告與錯誤會在 console 中大量出現,妨礙除錯。 | 在 process.env.NODE_ENV === 'production' 時才啟用遠端上報,開發時僅 console.warn。 |
| 缺乏錯誤上下文 | 只回報訊息與堆疊,無法定位是哪個 component、哪筆資料觸發。 | 在 payload 中加入 component 名稱、props、路由資訊,如前面的 GlobalErrorPayload。 |
最佳實踐總結:
- 全域註冊一次:在
main.js立即設定app.config.errorHandler,避免在子組件重複註冊。 - 分層上報:先在本地顯示友善訊息,然後非同步上報;若上報失敗,寫入離線緩衝。
- 結合第三方服務:如 Sentry、BugSnag,可省去自行建構 UI 與聚合分析的成本。
- 保留開發階段的完整堆疊:在開發環境不要過度壓縮錯誤資訊,方便快速定位問題。
- 測試錯誤流程:使用 Jest、Cypress 撰寫「故意拋錯」的測試案例,驗證全域 handler 是否正確觸發。
實際應用場景
| 場景 | 為什麼需要全域錯誤處理 | 實作要點 |
|---|---|---|
| 使用第三方 UI 套件(如 Element‑Plus) | 套件內部可能因不相容的瀏覽器或資料格式拋出例外,若未捕捉會導致整個頁面卡死。 | 在 errorHandler 中記錄 component 為套件名稱,並在 UI 中顯示「系統暫時無法顯示此區塊」的 fallback。 |
| 大量異步 API 呼叫 | 任何一次失敗都可能導致資料不完整,使用者可能看到空白表格。 | 在每個 API 呼叫的 .catch() 中手動上報,同時在 unhandledrejection 捕捉漏網之魚。 |
| 離線可用的 PWA | 使用者在離線狀態下仍可能觸發錯誤(如 IndexedDB 讀寫失敗)。 | 在 errorHandler 判斷 navigator.onLine,若離線則寫入本地緩衝,待 online 事件觸發時統一上報。 |
| 多語系或動態路由 | 錯誤訊息若未翻譯會影響使用者體驗。 | 在 errorHandler 內根據 router.currentRoute.value.meta.locale 取得當前語系,並使用 i18n 產生本地化的 toast。 |
| 管理後台儀表板 | 後台操作常涉及大量表單驗證與即時資料更新,錯誤必須即時告知管理者。 | 在 errorHandler 中加入 userId、role 等欄位,讓後端可以針對特定帳號做錯誤分析。 |
總結
全域錯誤邏輯是 Vue3 應用穩定性的基石。透過 app.config.errorHandler、window.onunhandledrejection 以及結構化的錯誤 payload,我們可以:
- 即時捕捉 所有未處理的例外與 Promise 錯誤
- 提供使用者友善的回饋,避免畫面卡死或無回應
- 將錯誤資訊上報 給遠端監控平台,縮短排除時間
- 支援離線緩衝,確保在網路不穩時仍不遺失關鍵錯誤資料
在實作時,務必注意不要在錯誤處理本身產生新錯誤、保持上報的非同步性、以及加入足夠的上下文資訊。結合第三方服務(如 Sentry)或自行打造的 API,都能讓錯誤管理更完整、除錯更高效。
掌握了這套全域錯誤處理機制,你的 Vue3 專案將具備 更好的使用者體驗、更快的問題定位,也能在日益複雜的前端生態系中,保持彈性與可靠性。祝開發順利,錯誤遠離!