Vue3 – 錯誤處理與除錯:在 setup 中使用 try / catch
簡介
在 Vue 3 中,setup 是組件的入口點,所有的 響應式狀態、生命週期、Composition API 都在此建立。雖然 setup 看似簡潔,但它同樣會遭遇非同步請求、外部函式拋出的例外或程式邏輯錯誤。若未妥善捕捉,錯誤會直接導致整個組件渲染失敗,甚至影響整個應用程式的穩定性。
使用 try / catch 於 setup 中處理例外,讓我們可以:
- 即時回報錯誤(例如顯示 toast、寫入日誌)。
- 提供備援資料,避免 UI 卡住或出現空白。
- 保持組件生命週期的完整,即使某段程式失敗,其他功能仍能正常運作。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你完整掌握在 setup 裡使用 try / catch 的技巧,幫助你寫出更健壯的 Vue 3 應用。
核心概念
1. 為什麼在 setup 中需要 try / catch
setup 會在 組件建立階段 同步執行,隨後返回的物件會被 Vue 用來建立模板的渲染上下文。若 setup 內的程式碼拋出未捕捉的例外,Vue 會直接中止組件的掛載,導致:
- 畫面渲染失敗(白屏或錯誤訊息)。
- 全局錯誤捕捉器(
app.config.errorHandler)才會被觸發,卻失去對單一組件的精細控制。
因此,將可能拋出例外的程式碼包裹在 try / catch,可以在組件層級先行處理錯誤,提供更好的使用者體驗。
2. 同步 vs. 非同步錯誤
- 同步錯誤:在執行時立即拋出,例如 JSON 解析失敗、陣列索引超界。
- 非同步錯誤:發生於
Promise、async/await、setTimeout等非同步流程。
對於非同步錯誤,必須在 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讓我們可以直接在掛載階段awaitAPI。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 可根據
uploadStatus與error直接呈現不同的狀態。
範例 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顯示相應訊息。
常見陷阱與最佳實踐
不要把所有程式碼都包在同一個
try裡- 大範圍的
try會讓錯誤定位變困難,且可能捕獲不必要的例外。 - 建議 局部化 捕捉,僅針對可能失敗的區段使用
try / catch。
- 大範圍的
async setup會讓組件的掛載變成「等待」- 若
setup內的await時間過長,整個畫面會卡在 loading 狀態。 - 使用 loading 狀態(如
ref(true))或 Skeleton UI 來提升使用者體驗。
- 若
錯誤資訊不要直接暴露給使用者
- 從後端回傳的錯誤訊息往往過於技術化,應轉換為 友善語句。
- 同時保留完整錯誤堆疊以供 log 或 上報。
避免在
catch裡再次拋出未處理的錯誤- 若真的需要把錯誤往上層傳遞,請使用
throw並確保上層有相對應的捕捉機制(如全局errorHandler)。
- 若真的需要把錯誤往上層傳遞,請使用
使用
finally釋放資源- 如 取消訂閱、清除計時器、關閉 WebSocket,都應放在
finally區塊,確保即使發生錯誤也不會留下資源洩漏。
- 如 取消訂閱、清除計時器、關閉 WebSocket,都應放在
在 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,同時提供 loading、errorMsg 變數。 |
| 表單提交前的資料驗證 | 客戶端驗證失敗、第三方驗證服務拋例外 | 在表單提交函式內部局部 try,捕捉驗證錯誤並回饋給使用者。 |
| WebSocket 連線 | 連線失敗、訊息解析錯誤 | onMounted 中建立連線,使用 try 包住 new WebSocket(),catch 中顯示斷線提示。 |
| 動態匯入 (lazy load) 組件 | 匯入檔案不存在或網路斷線 | defineAsyncComponent(() => import('./HeavyComp.vue')).catch(err => ...),或在 setup 中 await import 包 try. |
| 瀏覽器 API (如 Geolocation、Clipboard) | 使用者拒絕授權、API 不支援 | try { await navigator.geolocation.getCurrentPosition(...) } catch (e) { ... },確保 UI 不會卡死。 |
總結
在 Vue 3 的 Composition API 中,setup 是組件的核心入口,錯誤處理 更是不可或缺的環節。透過以下要點,你可以寫出更穩定、使用者體驗更佳的程式碼:
- 針對同步與非同步錯誤分別使用
try / catch,必要時結合Promise.catch。 - 局部化捕捉,避免整個
setup被一次性捕獲,提升除錯效率。 - 提供 UI 反饋(loading、errorMsg、status)讓使用者知道發生了什麼事。
- 善用
finally釋放資源、關閉計時器,防止資源泄漏。 - 結合全局錯誤處理器,同時滿足本地 UI 需求與統一上報需求。
掌握這些技巧後,你將能在開發 Vue 3 應用時,更從容地面對各種例外情境,讓產品在面對不確定的外部環境時依然保持穩定。祝開發順利,錯誤處理無懈可擊! 🚀