一、为什么必须做 Vue 全局错误处理?
在实际项目开发中,我们经常遇到这些问题:
- 每个接口、每个函数都写 try/catch,代码臃肿重复
- 错误提示散落在各个组件,风格不统一、难以收敛
- 线上异常无法统一上报,出问题难以定位
- 登录过期、权限不足等通用逻辑无法集中处理
全局错误处理的核心价值:
- 解耦业务与错误:业务代码只关心正常流程,错误统一收口
- 统一异常策略:提示、跳转、重试、上报全局控制
- 提供监控入口:所有错误集中收集,便于线上复盘与优化
- 提升用户体验:异常不崩溃、不白屏、友好提示
二、Vue3 基础:官方全局错误入口
Vue3 提供了标准的全局错误捕获配置:app.config.errorHandler,它是整个体系的基石。
2.1 基础使用
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 全局错误捕获
app.config.errorHandler = (err, instance, info) => {
console.error('全局捕获到异常:', err, instance, info)
}
app.mount('#app')
2.2 它能捕获哪些错误?
- 组件 setup /render 函数内同步错误
- 生命周期钩子(onMounted、onUpdated 等)抛出错误
- 模板渲染、指令执行异常
- watch /computed 中抛出的错误
- Vue 管理的 Promise 链错误
2.3 它不能捕获哪些错误?
- setTimeout /setInterval 内抛出的错误
- 原生 fetch/axios 未捕获的 Promise Reject
- 主动 new Promise ().reject () 且未 catch
- 页面资源加载失败(img/script/css 404)
2.4 参数说明
app.config.errorHandler = (err, instance, info) => {
// err:错误对象(message、stack)
// instance:报错组件实例(可拿到 props、data等)
// info:错误来源信息,如:render、watch、lifecycle hook、function
}
三、工程化第一步:定义可识别的业务错误
要实现优雅处理,必须先标准化错误结构,而不是只抛字符串。
// src/constants/errorTypes.js
export class BusinessError extends Error {
constructor(message, code, data = null) {
super(message)
this.code = code
this.data = data
this.isBusinessError = true
this.name = 'BusinessError'
}
}
// 登录过期
export class AuthExpiredError extends BusinessError {
constructor(message = '登录已过期,请重新登录') {
super(message, 'AUTH_EXPIRED')
}
}
// 权限不足
export class PermissionDeniedError extends BusinessError {
constructor(message = '您没有此操作权限') {
super(message, 'PERMISSION_DENIED')
}
}
// 业务校验失败
export class ValidateError extends BusinessError {
constructor(message = '数据校验不通过', data) {
super(message, 'VALIDATE_FAILED', data)
}
}
这样在业务中抛出的错误,全局层就能精准识别类型。
四、工程化第二步:全局错误分类处理
统一入口 + 策略分发,避免一堆 if else。
4.1 错误处理策略表(最优雅写法)
// src/utils/errorHandler.js
import { ElMessage } from 'element-plus'
import router from '@/router'
import { reportError } from './errorReport'
// 错误策略表
const errorStrategy = {
// 登录过期
AUTH_EXPIRED: (err) => {
ElMessage.warning(err.message)
router.push('/login')
},
// 权限不足
PERMISSION_DENIED: (err) => {
ElMessage.error(err.message)
},
// 校验失败
VALIDATE_FAILED: (err) => {
ElMessage.info(err.message)
},
// 默认兜底
default: (err) => {
ElMessage.error('系统异常,请稍后重试')
reportError(err) // 上报监控
}
}
export function registerGlobalError(app) {
app.config.errorHandler = (err, instance, info) => {
console.error('[全局错误]', err, instance, info)
// 附加错误上下文
err.context = {
component: instance?.$options?.name || instance?.type?.name || 'unknown',
trigger: info
}
// 策略匹配执行
const handler = errorStrategy[err.code] || errorStrategy.default
handler(err)
}
}
4.2 在 main.js 注册
import { createApp } from 'vue'
import App from './App.vue'
import { registerGlobalError } from '@/utils/errorHandler'
const app = createApp(App)
registerGlobalError(app)
app.mount('#app')
五、补齐短板:捕获 Promise 与异步错误
errorHandler 不捕获原生 Promise,我们用全局事件补齐。
// src/utils/errorHandler.js
export function registerPromiseErrorHandler(app) {
// 全局未捕获的 Promise Reject
window.addEventListener('unhandledrejection', (event) => {
event.preventDefault() // 阻止浏览器默认提示
const err = event.reason
// 抛给 Vue 全局错误处理统一消化
app.config.errorHandler(err, null, 'unhandledRejection')
})
}
在 main.js 同时注册:
registerGlobalError(app)
registerPromiseErrorHandler(app)
这样:axios/fetch 错误、定时器错误、异步异常全部能捕获。
六、必备能力:错误上报与监控
线上项目必须可观测,上报错误堆栈、环境、用户信息。
// src/utils/errorReport.js
export function reportError(err) {
const data = {
message: err.message,
code: err.code,
stack: err.stack?.substring(0, 500), // 堆栈过长截取
component: err.context?.component,
trigger: err.context?.trigger,
url: location.href,
ua: navigator.userAgent,
time: new Date().toISOString()
}
// 上报后端
fetch('/api/error/report', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).catch(() => {})
}
七、与 Axios 结合:接口错误统一收口
把接口异常也纳入全局体系,业务完全零感知。
// src/utils/request.js
import axios from 'axios'
import { AuthExpiredError, PermissionDeniedError } from '@/constants/errorTypes'
const service = axios.create({ baseURL: '/api' })
service.interceptors.response.use(
(res) => res.data,
(error) => {
const status = error.response?.status
if (status === 401) {
return Promise.reject(new AuthExpiredError())
}
if (status === 403) {
return Promise.reject(new PermissionDeniedError())
}
return Promise.reject(error)
}
)
export default service
业务代码彻底清爽:
const fetchData = async () => {
const data = await api.getUserList() // 无需try/catch
list.value = data
}
八、最终效果:业务与错误完全解耦
业务组件只写核心逻辑:
javascript
<script setup>
import api from '@/utils/request'
import { onMounted } from 'vue'
const list = ref([])
onMounted(async () => {
const data = await api.getList()
list.value = data
})
</script>
所有异常:
- 401 → 自动跳登录
- 403 → 自动提示无权限
- 其他 → 统一提示 + 自动上报
- 业务代码完全干净
九、总结
一套完整的 Vue 全局错误体系包括:
- 统一入口:app.config.errorHandler
- 标准错误类型:BusinessError 及其子类
- 策略分发:策略表模式,易扩展、易维护
- 全覆盖捕获:errorHandler + unhandledrejection
- 统一提示与跳转:体验一致
- 统一上报:线上可监控、可复盘
最终实现:业务代码只管业务,错误交给全局,架构清晰、长期不腐化。