React|React 内的错误捕获
React Error Catcher
React 内不同类型的错误捕获本文将会从三个阶段来探讨发生在在 React 内的错误捕获,并且介绍如何封装一个通用的组件:
- React 内主要错误根因和错误捕获方法
- 对捕获错误的数据处理
- 捕获组件设计
文章图片
Keyword:
React Error Boundary
ErrorEvent
Error Information
- Error 展示和数据分析
- npm
- github
不如我们先了解 JavaScript 内一些常见的错误,这会在我们开发组件时提供帮助
// 一个简单的抛出异常,可以在脚本任何地方,会被 `onError` 捕获
const err = new Error('crash!')try {
throw(err);
} catch(e) {
console.log(e);
}// 利用 setTimeout 抛出异步错误,渲染完成时触发
componentDidMount() {
setTimeout(() => {
const e = new Error('111');
throw(e);
}, 100);
}// 由于用户交互引起的事件错误,在编译时不被察觉,在执行时产生
// `onError` 捕获
clickValue = https://www.it610.com/article/(value: string) => {
JSON.parse(JSON.parse(value));
}// 异步事件错误,即 Promise.reject()
// `unhandledrejection` 捕获
(async (value: string) => {
await JSON.parse(JSON.parse(value));
})("hello")// 组件渲染错误,我们可以直接在 React JSX 内返回一个错误
render() {
return (
<>new Error("hello")<\/>
)
}
在 React 内错误根据其表现类型可以分为:
- 渲染错误,即在渲染阶段发生异常,比如某个组件没有引入就使用
- 引用错误,即引入某个资源文件时发生错误,这个往往在编译过程中能够捕获到。在这里我们讨论异步引入的情况
- 事件处理,即渲染没问题,但是在在调用其触发事件时发生错误,比如
JSON.parse(JSON.parse("some reason"))
,这类错误可以细分为用户手动触发和脚本触发,可以参考Error.isTrusted
属性进行理解 - 异步代码,比如
promise.reject("some reason")
错误和错误的捕获方法存在一对多的关系,即一个错误可能被不同的方法捕获,在 JavaScript 内错误事件捕获的方法各司其职:
- 使用
try {} catch(e){}
语句在事件处理器内部捕获错误 React Error boundaries
,可以捕获资源引用和组件渲染错误,通常这两者在 dev 时就能发现window.addEventListener('error')
,可以捕获事件处理错误window.addEventListener('unhandledrejection')
,可以捕获异步代码错误
Error boundaries
Error Boundaries 是 React 提出的一种用于错误捕获的组件,事实上,它是一个的定义了
static getDerivedStateFromError()
或 componentDidCatch()
生命周期方法的 class 组件Error Boundaries 的出现是为了解决:部分 UI 的错误导致整个 App 的崩溃的问题,比如一个页面内,侧边栏出现的问题导致整个页面无法显示,此时可以通过捕获侧边栏的错误,渲染出降级的 UI(或者另一个方案)来避免这种情况
Error boundaries 可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误并渲染出备用 UI
【React|React 内的错误捕获】以下情况下不能通过 Error Boundaries 来 catch 错误(但是可以通过上述其他手段来获取):
- 组件的内部的事件处理函数,因为 Error Boundaries 处理的仅仅是 Render 中的错误,而 Handle Event 并不发生在 Render 过程中
- 异步函数中的异常,比如
setTimeout
或者setInterval
,requestAnimationFrame
等函数中的异常 - 服务端渲染
ErrorEvent of JavaScript
通过
onerror
捕获的错误类型为 ErrorEvent ,ErrorEvent 继承于 Event,其自身属性包括:message: string
错误的描述信息filename: string
发生错误的文件名lineno: number
错误发生的行号colno: number
错误发生的列号error: Error
error 实例
Event.currentTarget
标识的是事件沿着 DOM 触发时的当前目标,它指向的是事件绑定元素(因为有可能在触发过程中被改变),Event.target
指向的是事件触发元素isTrusted: boolean
表示事件是由浏览器(比如用户点击)发起(true)的还是由脚本(使用事件创建方法)引起的(false)timestamp
不同浏览器对其定义不一致,因此通常不要使用这个参数来作为时间记录target.performance.timeOrigin
表示开始记录的高精度 timestamp
onunhandledrejection
捕获的错误类型为 PromiseRejectionEvent ,它出现在 JavaScript 内 promise
被 reject
时触发事件,其同样继承于 Event,其自身属性包括:promise
reason
表示 promise 为什么被 rejected
onerror
和 onunhandledrejection
的信息通常通过 Event.target
提供,而对于 React Error Boundaries 捕获的错误,通常从 window
对象来获取下面针对一些关键的错误信息进行梳理:
export interface ErrorInfo {
app?: string
// "onerror" | "onunhandledrejection" | "componentDidCatch"
caughtEvent?: string
user?: string
message?: string
// window.performance.timeOrigin 时间戳
timeOrigin?: number | string
// at filepath lineno:colno
stack?: string
// event.type 事件类型
type?: string
// event.isTrusted 事件触发来源
isTrusted?: boolean
// 是否启用 cookie
cookieEnabled?: boolean
cookie?: string
userAgent?: string
href?: string
screenHeight?: number | string
screenWidth?: number | string
}
Create a component 当我们具备了捕获错误的能力和整理错误信息的能力之后,封装组件就是一件水到渠成的事情了,大家轻松封装出一个组件,因此我就交流一下在处理数据过程中的一些问题:
- 重复数据的处理。一个错误往往可能在同一时间触发多个捕获事件,我们可以通过关键字段和
Set
类型来进行去重处理,我会选择app
、user
、timeOrigin
和caughtEvent
组合成为一个标志符来进行判断 - 过滤指定错误。因为通常错误不是由我们开发产生的,可能是由于引入的组件引起的,比如
antd
部分组件抛出的ResizeObserver loop limit exceeded
错误,对于组件使用没有实际影响。对于这类错误,我们会提供过滤的功能,用来过滤掉指定的错误。同时,为了防止组件本身内部引起的错误循环上报,我们可以抛出一个自定义的错误,并将其进行默认过滤 - 批量上报。前端错误的产生往往是在一瞬间同时发生的,因此我们不得不考虑进行批量上报。我会从时间上(通过设置定时器任务)和数量上来进行约束
推荐阅读
- 投稿|「东亚卷王」稻盛和夫的奇幻漂流
- APISpace 中文分词API
- springboot+vue项目部署内网穿透
- Debug生涯|seaborn urllib.error.URLError: < urlopen error [WinError 10054] 远程主机强迫关闭了一个现有的连接。>
- object.变量报错,object.和object[]的区别()
- 机器学习算法|图像处理中的经典机器学习方法
- 投稿|“先进”的飞书为何搞不定钉钉?
- 投稿|零跑IPO:研发投入不足,如何画圆全域自研的大饼
- 投稿|电子测量仪器:一个隐秘的“卡脖子”行业
- 投稿|摆脱宁德时代阻击,IPO后的中创新航恐难“远航”