错误边界组件
错误边界简介
错误边界(Error Boundaries)是 17 年时 React 中出现的一种概念,它指出:组件渲染、生命周期时出现的错误不应导致整个应用的崩溃[1]。使用错误边界能捕获组件的错误并回退到特定组件。
Vue 中的错误捕获
VueJS 内置了两种选项用于捕获组件中出现的错误,errorHandler 和 errorCaptured(Vue@2.5+),分别用于设置全局的错误捕获与子孙组件的错误捕获。VueJS 自定义了错误的传播规则:只要 errorCaptured 没有返回 false,错误就会一直向上传播:

有两点需要注意:
- errorHandler 会阻止错误冒泡到 window.error;
- errorCaptured 不能捕获组件自身中的错误;
针对以上两点,一般会这么处理:
- 使用自定义函数包装并增强 errorHandler 函数,组件内部不再使用 errorCaptured 处理异常。
- 封装以 errorCaptured 为基础的 ErrorBoundary 组件,控制报错粒度,并防止 UI 崩溃。
错误边界组件
先看看最基本的使用。
<template>
<ErrorBoundary :fall-back="uiFallBack">
<UnstableComponent />
</ErrorBoundary>
</template>
<script>
export default {
data () {
return {
uiFallBack: {
functional: true,
render (h) {
return h('div', [
h('h1', '网络错误~'),
h('p', '请稍后重试~')
])
}
}
}
}
}
</script>
源代码非常简单,直接贴上来啦。
<script>
/* 帮助函数 */
import { isObjectEmpty, warn, convertVNodeArray } from 'utils'
export default {
name: 'ErrorBoundary',
props: {
fallBack: Object,
onError: Function,
params: Object,
stopPropagation: Boolean,
tag: String
},
data() {
return {
err: '',
info: '',
hasError: null
};
},
// 当 ErrorBoundary 的子孙组件出错时,
// 触发 errorCaptured 事件,
// 并调用传入的 onError 处理异常
errorCaptured(err, vm, info = '') {
this.hasError = true
this.err = err
this.info = info
this.$emit('errorCaptured', { err, vm, info })
if (this.onError) this.onError(err, vm, info)
if (this.stopPropagation) return false
},
// 渲染的流程:
// 1. 有错误则尝试渲染 slots.boundary,
// 没有 slots 时,回退到由 props 传入的 fallBack 组件
// 2. 无错误则尝试渲染 slots.boundary,
// 没有 slots 时,回退到 没有 slots.defaults
render(h) {
const content = this.$slots.default
const isScoped = this.$scopedSlots.boundary
let scopedSlot
if (isScoped) {
scopedSlot = this.$scopedSlots.boundary({
hasError: this.hasError,
err: this.err,
info: this.info
})
}
const fallbackOrScoped = isScoped
? scopedSlot
: h(this.fallBack, {
props: {...this.params}
})
if (this.hasError) {
return Array.isArray(fallbackOrScoped)
? convertVNodeArray(h, this.tag, fallbackOrScoped)
: fallbackOrScoped
}
if (isScoped) {
if (!this.$scopedSlots.boundary()) {
warn('ErrorBoundary component must have child components.')
return null
}
return Array.isArray(scopedSlot)
? convertVNodeArray(h, this.tag, scopedSlot)
: scopedSlot
}
if (isObjectEmpty(this.$slots)) {
warn('ErrorBoundary component must have child components.')
return null;
}
return Array.isArray(content)
? convertVNodeArray(h, this.tag, content)
: content
}
}
</script>
其它的捕获错误思路
一般而言,只要代码中抛出的错误,没有被拦截,最终都会被 window.onerror 捕获到——所以需要强调以下特殊情况:
- 像 errorHandler 这种 API 会拦截错误,所以使用自定义函数对框架 API 进行增强。
- 特殊错误如 Promise 中的异常,需要使用 window.addEventListener('unhandledrejection', event => ···) 进行捕获。
阅读更多
https://reactjs.org/docs/error-boundaries.html#gatsby-focus-wrapper ↩︎