学完这篇,你能把 Vue 3 里散落在各处的报错统一收拢到一个出口——该拦的拦、该上报的上报,而不是让浏览器控制台一片红、用户白屏了你还不知道。
第一步:先认清 Vue 3 的错误有哪几条路
Vue 3 内部所有能被它感知的错误,最终都会走 handleError,分流到三个地方:组件的 errorCaptured 钩子(从出错组件开始逐层往上冒)、全局的 app.config.errorHandler、最后才是控制台的 logError。
所以你要处理的错误其实分三类:
- Vue 管得到的:渲染函数出错、
setup 里同步抛错、生命周期钩子抛错、事件处理器的同步错误。
- Vue 半管得到的:事件处理器是
async 函数时,返回的 Promise 被 reject,Vue 会挂一个 catch 转给 handleError,能捕获到。
- Vue 管不到的:
setTimeout、独立的 new Promise()、fetch().then() 里自己没包 try/catch 的错误,以及第三方库内部异步回调的报错。
第二步:配置全局 errorHandler 兜底
在 main.js 里创建应用后立刻挂上:
const app = createApp(App)
app.config.errorHandler = (err, instance, info) => {
console.error('[global]', info, err)
// 上报:err.message / err.stack / info / instance?.$options.name
}
app.mount('#app')
三个参数分别是错误对象、出错的组件实例(可能为 null)、错误来源标识(形如 "render function"、"setup function"、"lifecycle hook (mounted)"、"v-on handler"、"component event handler")。info 是做错误分类统计最有价值的字段,别丢。
注意:千万不要在 errorHandler 里再抛出错误。它自身报错会再次触发 handleError,直接进入无限递归,页面卡死。上报逻辑一律包 try/catch,或者扔进队列异步上报。
第三步:用 onErrorCaptured 做局部拦截
有些模块你不想让错误直接冒到全局——比如一个侧边栏挂了不该让整个页面崩。在父组件里:
import { onErrorCaptured, ref } from 'vue'
const failed = ref(false)
onErrorCaptured((err, instance, info) => {
console.warn('局部捕获', info, err)
failed.value = true
return false // 关键:阻止继续向上传播
})
配合 <template v-if="!failed"> 换成降级 UI,就是一个最小的错误边界。选项式 API 里对应 errorCaptured(err, instance, info) 钩子,返回值规则一样。
返回值的规则记牢:
- 返回
false —— 阻止错误继续向上冒泡,全局 errorHandler 不再收到。
- 返回其他任何值(含
undefined)—— 正常向上传播。
- 钩子自己抛错 —— 这个新错误会继续往上冒,原本的错误反而被吞掉。
注意:errorCaptured 捕获不到子组件里 setTimeout、异步 Promise 链中的错误,它只处理 Vue 调用栈内的同步错误。别指望它当万能兜底。
第四步:用 window 事件补最后一层网
异步和框架外的错误只能靠浏览器全局事件:
window.addEventListener('error', (e) => {
// 资源加载失败也能到这里,注意区分
if (e.target && e.target !== window) return
report(e.error || { message: e.message })
})
window.addEventListener('unhandledrejection', (e) => {
report(e.reason)
})
注意 error 事件在资源(<img>、<script>)加载失败时也会触发,且 e.target 不是 window,用上面那行判断过滤掉,否则一堆 404 图片会污染你的错误报表。
另外这两个监听要在 app.mount() 之前注册,否则首屏渲染期间的错误会漏掉。
第五步:上报前做去重和限流
同一处渲染错误在一秒内可能触发几十次(尤其在循环里)。上报前至少做两件事:
- 按
err.message + info 生成指纹,同一指纹 5 秒内只报一次。
- 单页面上报总数封顶(比如 10 条),超了只记一条「错误过多已截断」。
报文体建议带:错误指纹、err.stack、info、当前路由 fullPath、用户标识、UA。没有路由和 info,线上排查基本等于盲猜。
第六步:开发期打开 warnHandler
if (import.meta.env.DEV) {
app.config.warnHandler = (msg, instance, trace) => {
console.warn(msg + trace)
}
}
生产环境这个配置不生效,放心写。它帮你揪出 prop 类型不对、缺 key 这类不会抛错但会埋雷的问题。
小结
- Vue 3 的错误出口是
app.config.errorHandler,参数 (err, instance, info),info 必须一起上报。
onErrorCaptured / errorCaptured 做局部兜底,返回 false 才阻断冒泡,配合降级 UI 才是真正的错误边界。
- 事件处理器的
async 函数错误 Vue 能捕获,setTimeout 和自建 Promise 链捕获不到。
- 用
window.addEventListener('error' / 'unhandledrejection') 补最后一层,注册要早于 mount。
errorHandler 内禁止抛错,上报要做指纹去重和次数封顶。