本文 AI 產出,尚未審核

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。

最佳實踐總結:

  1. 全域註冊一次:在 main.js 立即設定 app.config.errorHandler,避免在子組件重複註冊。
  2. 分層上報:先在本地顯示友善訊息,然後非同步上報;若上報失敗,寫入離線緩衝。
  3. 結合第三方服務:如 Sentry、BugSnag,可省去自行建構 UI 與聚合分析的成本。
  4. 保留開發階段的完整堆疊:在開發環境不要過度壓縮錯誤資訊,方便快速定位問題。
  5. 測試錯誤流程:使用 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 專案將具備 更好的使用者體驗、更快的問題定位,也能在日益複雜的前端生態系中,保持彈性與可靠性。祝開發順利,錯誤遠離!