【Vue3 实战】全局错误处理体系搭建:实现业务与错误彻底解耦

一、为什么必须做 Vue 全局错误处理?

在实际项目开发中,我们经常遇到这些问题:

  • 每个接口、每个函数都写 try/catch,代码臃肿重复
  • 错误提示散落在各个组件,风格不统一、难以收敛
  • 线上异常无法统一上报,出问题难以定位
  • 登录过期、权限不足等通用逻辑无法集中处理

全局错误处理的核心价值

  1. 解耦业务与错误:业务代码只关心正常流程,错误统一收口
  2. 统一异常策略:提示、跳转、重试、上报全局控制
  3. 提供监控入口:所有错误集中收集,便于线上复盘与优化
  4. 提升用户体验:异常不崩溃、不白屏、友好提示

二、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 全局错误体系包括:

  1. 统一入口:app.config.errorHandler
  2. 标准错误类型:BusinessError 及其子类
  3. 策略分发:策略表模式,易扩展、易维护
  4. 全覆盖捕获:errorHandler + unhandledrejection
  5. 统一提示与跳转:体验一致
  6. 统一上报:线上可监控、可复盘

最终实现:业务代码只管业务,错误交给全局,架构清晰、长期不腐化。

相关推荐
悟空瞎说2 小时前
# Git 交互式变基:优雅整理提交历史,告别杂乱 PR 记录
前端·git
竹林8183 小时前
从ethers.js迁移到Viem:我在DeFi Dashboard项目中踩过的坑与最终方案
javascript
还有多久拿退休金3 小时前
DragSortTable:一个让我怀疑人生的滚动重置 Bug
前端
zithern_juejin3 小时前
ES6——Promise
javascript
渐儿3 小时前
组件库开发入门到生产(从零封装到 npm 发布)
前端
KaMeidebaby3 小时前
卡梅德生物技术快报|单 B 细胞抗体制备:流程优化、表达系统适配与性能数据
前端·数据库·其他·百度·新浪微博
桜吹雪3 小时前
所有智能体架构(1):反思 (Reflection)
javascript·人工智能
lichenyang4533 小时前
从鸿蒙 AI 聊天 Demo 学习 ArkUI V2:第一天上手记录
前端
进击的松鼠4 小时前
OpenClaw 的五层架构设计与解析
前端·架构·agent