本文 AI 產出,尚未審核

Vue3 – 錯誤處理與除錯:在 setup 中使用 try / catch


簡介

在 Vue 3 中,setup 是組件的入口點,所有的 響應式狀態生命週期Composition API 都在此建立。雖然 setup 看似簡潔,但它同樣會遭遇非同步請求、外部函式拋出的例外或程式邏輯錯誤。若未妥善捕捉,錯誤會直接導致整個組件渲染失敗,甚至影響整個應用程式的穩定性。

使用 try / catchsetup 中處理例外,讓我們可以:

  1. 即時回報錯誤(例如顯示 toast、寫入日誌)。
  2. 提供備援資料,避免 UI 卡住或出現空白。
  3. 保持組件生命週期的完整,即使某段程式失敗,其他功能仍能正常運作。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你完整掌握在 setup 裡使用 try / catch 的技巧,幫助你寫出更健壯的 Vue 3 應用。


核心概念

1. 為什麼在 setup 中需要 try / catch

setup 會在 組件建立階段 同步執行,隨後返回的物件會被 Vue 用來建立模板的渲染上下文。若 setup 內的程式碼拋出未捕捉的例外,Vue 會直接中止組件的掛載,導致:

  • 畫面渲染失敗(白屏或錯誤訊息)。
  • 全局錯誤捕捉器app.config.errorHandler)才會被觸發,卻失去對單一組件的精細控制。

因此,將可能拋出例外的程式碼包裹在 try / catch,可以在組件層級先行處理錯誤,提供更好的使用者體驗。

2. 同步 vs. 非同步錯誤

  • 同步錯誤:在執行時立即拋出,例如 JSON 解析失敗、陣列索引超界。
  • 非同步錯誤:發生於 Promiseasync/awaitsetTimeout 等非同步流程。

對於非同步錯誤,必須在 async 函式內使用 try / catch,或在 Promise 鏈中加入 .catch()。以下範例會同時示範兩者。

3. setup 內的錯誤捕捉模式

模式 範例說明 何時使用
同步 try / catch 包裹直接執行的程式碼 初始化資料、檢查參數等
async setup + try / catch setup 本身宣告為 async,可直接 await 非同步操作 需要在掛載前完成 API 請求、資料庫讀取等
局部 try / catch 只針對特定函式或效能較高的區塊使用 想要把錯誤限制在局部,避免影響整體流程
全局錯誤處理 + 本地 catch 結合 app.config.errorHandler,在本地捕捉後仍可上報 需要統一上報錯誤,同時在 UI 上顯示友善訊息

程式碼範例

範例 1️⃣:同步錯誤捕捉 – 初始化參數檢查

import { ref } from 'vue'

export default {
  name: 'UserCard',
  props: {
    userId: {
      type: Number,
      required: true
    }
  },
  setup(props) {
    // ---------- 同步 try / catch ----------
    let parsedId
    try {
      // 假設從外部傳入的 userId 可能是字串,需要轉為數字
      parsedId = Number(props.userId)
      if (isNaN(parsedId)) throw new Error('userId 必須是數字')
    } catch (err) {
      console.error('[UserCard] 參數錯誤:', err)
      // 回傳一個安全的預設值,避免掛載失敗
      parsedId = 0
    }

    const user = ref(null)
    // 後續可使用 parsedId 進行 API 請求...
    return { user, parsedId }
  }
}

說明

  • try 捕捉 Number() 失敗或自訂的拋錯,確保 setup 不會因參數問題直接中斷。
  • 捕捉後回傳預設值 0,讓 UI 可以顯示「無效的使用者」提示,而不是整個畫面白屏。

範例 2️⃣:非同步錯誤捕捉 – async setup 取得遠端資料

import { ref } from 'vue'
import axios from 'axios'

export default {
  name: 'PostDetail',
  props: {
    postId: { type: Number, required: true }
  },
  // ----------- async setup ----------
  async setup(props) {
    const post = ref(null)
    const loading = ref(true)
    const errorMsg = ref('')

    try {
      const { data } = await axios.get(`/api/posts/${props.postId}`)
      post.value = data
    } catch (err) {
      console.error('[PostDetail] 取得文章失敗:', err)
      errorMsg.value = '抱歉,無法載入文章,請稍後再試。'
    } finally {
      loading.value = false
    }

    return { post, loading, errorMsg }
  }
}

說明

  • async setup 讓我們可以直接在掛載階段 await API。
  • try / catch 捕捉網路錯誤或伺服器回傳的非 2xx 狀態,並把錯誤資訊寫入 errorMsg,讓模板可以顯示友善訊息。
  • finally 確保 loading 狀態一定會被關閉。

範例 3️⃣:局部 try / catch – 事件處理函式中捕捉例外

import { ref } from 'vue'

export default {
  name: 'FileUploader',
  setup() {
    const files = ref([])
    const uploadStatus = ref('idle')
    const error = ref('')

    // ---------- 局部 try / catch ----------
    const handleFileSelect = async (e) => {
      const selected = e.target.files
      if (!selected.length) return

      try {
        uploadStatus.value = 'uploading'
        // 假設 uploadFile 為自訂的非同步上傳函式
        const result = await uploadFile(selected[0])
        files.value.push(result)
        uploadStatus.value = 'success'
      } catch (err) {
        console.error('[FileUploader] 上傳失敗:', err)
        error.value = '檔案上傳失敗,請檢查網路或檔案類型。'
        uploadStatus.value = 'error'
      }
    }

    return { files, uploadStatus, error, handleFileSelect }
  }
}

說明

  • 只在 handleFileSelect 事件處理器內捕捉錯誤,避免把整個 setup 包在大 try 裡,讓錯誤範圍更精確。
  • UI 可根據 uploadStatuserror 直接呈現不同的狀態。

範例 4️⃣:結合全局錯誤處理器與本地 catch

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

const app = createApp(App)

// 全局錯誤處理器:上報至後端或第三方服務
app.config.errorHandler = (err, vm, info) => {
  console.error('[Global] 錯誤上報:', err, info)
  // 例:呼叫 Sentry、LogRocket...
  // sendErrorToServer(err, info)
}

app.mount('#app')
// SomeComponent.vue
import { ref } from 'vue'
import fetchData from '@/api/fetchData'

export default {
  name: 'Dashboard',
  async setup() {
    const data = ref(null)
    const error = ref('')

    try {
      data.value = await fetchData()
    } catch (err) {
      // 本地捕捉後仍交給全局處理器
      error.value = '資料載入失敗,請稍後重試。'
      // 手動觸發全局 errorHandler(可選)
      // this.$app.config.errorHandler(err, this, 'Dashboard setup')
    }

    return { data, error }
  }
}

說明

  • 先在 main.js 設定全局錯誤處理,統一上報。
  • 在組件內部仍保留本地 catch,讓 UI 能即時回饋使用者,同時錯誤仍會被上報。

範例 5️⃣:使用 Promise.catch 搭配 ref 觀察錯誤

import { ref, onMounted } from 'vue'
import { getUserProfile } from '@/services/user'

export default {
  name: 'ProfileCard',
  setup() {
    const profile = ref(null)
    const error = ref(null)

    // onMounted 中使用 Promise.catch
    onMounted(() => {
      getUserProfile()
        .then(res => {
          profile.value = res
        })
        .catch(err => {
          console.warn('[ProfileCard] 取得使用者資料失敗:', err)
          error.value = '無法載入個人資料。'
        })
    })

    return { profile, error }
  }
}

說明

  • 若不想把 setup 變成 async,可以在生命週期鉤子內使用 Promise.catch
  • 同樣把錯誤寫入 ref,讓模板根據 error 顯示相應訊息。

常見陷阱與最佳實踐

  1. 不要把所有程式碼都包在同一個 try

    • 大範圍的 try 會讓錯誤定位變困難,且可能捕獲不必要的例外。
    • 建議 局部化 捕捉,僅針對可能失敗的區段使用 try / catch
  2. async setup 會讓組件的掛載變成「等待」

    • setup 內的 await 時間過長,整個畫面會卡在 loading 狀態。
    • 使用 loading 狀態(如 ref(true))或 Skeleton UI 來提升使用者體驗。
  3. 錯誤資訊不要直接暴露給使用者

    • 從後端回傳的錯誤訊息往往過於技術化,應轉換為 友善語句
    • 同時保留完整錯誤堆疊以供 log上報
  4. 避免在 catch 裡再次拋出未處理的錯誤

    • 若真的需要把錯誤往上層傳遞,請使用 throw 並確保上層有相對應的捕捉機制(如全局 errorHandler)。
  5. 使用 finally 釋放資源

    • 取消訂閱清除計時器關閉 WebSocket,都應放在 finally 區塊,確保即使發生錯誤也不會留下資源洩漏。
  6. 在 TypeScript 中配合 try / catch 使用類型斷言

    • catch 變數預設為 unknown,需要手動斷言為 Error 或自訂錯誤型別,以取得 .message.stack 等屬性。
try {
  // ...
} catch (e) {
  const err = e as Error
  console.error(err.message)
}

實際應用場景

場景 為什麼需要 try / catch 實作要點
從 API 取得使用者資料 網路不穩、授權失效、回傳格式錯誤 async setup 使用 try / catch,同時提供 loadingerrorMsg 變數。
表單提交前的資料驗證 客戶端驗證失敗、第三方驗證服務拋例外 在表單提交函式內部局部 try,捕捉驗證錯誤並回饋給使用者。
WebSocket 連線 連線失敗、訊息解析錯誤 onMounted 中建立連線,使用 try 包住 new WebSocket()catch 中顯示斷線提示。
動態匯入 (lazy load) 組件 匯入檔案不存在或網路斷線 defineAsyncComponent(() => import('./HeavyComp.vue')).catch(err => ...),或在 setupawait importtry.
瀏覽器 API (如 Geolocation、Clipboard) 使用者拒絕授權、API 不支援 try { await navigator.geolocation.getCurrentPosition(...) } catch (e) { ... },確保 UI 不會卡死。

總結

在 Vue 3 的 Composition API 中,setup 是組件的核心入口,錯誤處理 更是不可或缺的環節。透過以下要點,你可以寫出更穩定、使用者體驗更佳的程式碼:

  1. 針對同步與非同步錯誤分別使用 try / catch,必要時結合 Promise.catch
  2. 局部化捕捉,避免整個 setup 被一次性捕獲,提升除錯效率。
  3. 提供 UI 反饋(loading、errorMsg、status)讓使用者知道發生了什麼事。
  4. 善用 finally 釋放資源、關閉計時器,防止資源泄漏。
  5. 結合全局錯誤處理器,同時滿足本地 UI 需求與統一上報需求。

掌握這些技巧後,你將能在開發 Vue 3 應用時,更從容地面對各種例外情境,讓產品在面對不確定的外部環境時依然保持穩定。祝開發順利,錯誤處理無懈可擊! 🚀