【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分钟速写快排8 分钟前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby606141 分钟前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角2 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!2 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰2 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi4 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen4 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马5 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu5 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss
IT_陈寒5 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端