Error Boundaries是这么实现的,还挺巧妙
source link: https://segmentfault.com/a/1190000041057856
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
大家好,我卡颂。
本文会讲解React
中Error Boundaries
的完整实现逻辑。
一张图概括:
这里简单讲解下React
工作流程,后文有用。分为三步:
- 触发
更新
- render阶段:计算
更新
会造成的副作用
- commit阶段:在宿主环境执行
副作用
副作用
有很多,比如:
- 插入
DOM
节点 - 执行
useEffect
回调
好了,让我们进入主题。
欢迎加入人类高质量前端框架群,带飞
什么是Error Boundaries
React
提供了两个与错误处理相关的API
:
getDerivedStateFromError
:静态方法,当错误发生后提供一个机会渲染fallback UI
componentDidCatch
:组件实例方法,当错误发生后提供一个机会记录错误信息
使用了这两个API
的ClassComponent
通常被称为Error Boundaries
(错误边界)。
在Error Boundaries
的子孙组件中发生的所有React工作流程内的错误都会被Error Boundaries
捕获。
通过开篇的介绍可以知道,React工作流程指:
- render阶段
- commit阶段
考虑如下代码:
class ErrorBoundary extends Component { componentDidCatch(e) { console.warn(“发生错误”, e); } render() { return <div>{this.props.children}</div>; } } const App = () => ( <ErrorBoundary> <A><B/></A> <C/> <ErrorBoundary> )
A
、B
、C
作为ErrorBoundary
的子孙组件,当发生React工作流程内的错误,都会被ErrorBoundary
中的componentDidCatch
方法捕获。
步骤1:捕获错误
首先来看工作流程中的错误都是何时被捕获的。
render
阶段的核心代码如下,发生的错误会被handleError
处理:
do { try { // 对于并发更新则是workLoopConcurrent workLoopSync(); break; } catch (thrownValue) { handleError(root, thrownValue); } } while (true);
commit
阶段包含很多工作,比如:
componentDidMount/Update
执行- 绑定/解绑
ref
useEffect/useLayoutEffect
callback
与destroy
执行
这些工作会以如下形式执行,发生的错误被captureCommitPhaseError
处理:
try { // …执行某项工作 } catch (error) { captureCommitPhaseError(fiber, fiber.return, error); }
步骤2:构造callback
可以发现,即使没有Error Boundaries
,工作流程中的错误已经被React
捕获了。而正确的逻辑应该是:
- 如果存在
Error Boundaries
,执行对应API
- 抛出
React
的提示信息 - 如果不存在
Error Boundaries
,抛出未捕获的错误
所以,不管是handleError
还是captureCommitPhaseError
,都会从发生错误的节点的父节点开始,逐层向上遍历,寻找最近的Error Boundaries
。
一旦找到,就会构造:
- 用于执行Error Boundaries API的
callback
- 用于抛出React提示信息的
callback
// ...为了可读性,逻辑有删减 function createClassErrorUpdate() { if (typeof getDerivedStateFromError === 'function') { // 用于执行getDerivedStateFromError的callback update.payload = () => { return getDerivedStateFromError(error); }; // 用于抛出React提示信息的callback update.callback = () => { logCapturedError(fiber, errorInfo); }; } if (inst !== null && typeof inst.componentDidCatch === 'function') { // 用于执行componentDidCatch的callback update.callback = function callback() { this.componentDidCatch(error); }; } return update; }
如果没有找到Error Boundaries
,继续向上遍历直到根节点。
此时会构造:
- 用于抛出未捕获错误的
callback
- 用于抛出React提示信息的
callback
// ...为了可读性,逻辑有删减 funffction createRootErrorUpdate() { // 用于抛出“未捕获的错误”及“React的提示信息”的callback update.callback = () => { onUncaughtError(error); logCapturedError(fiber, errorInfo); }; return update; }
执行callback
构造好的callback
在什么时候执行呢?
在React
中有两个执行用户自定义callback的API
:
- 对于
ClassComponent
,this.setState(newState, callback)
中newState
和callback
参数都能传递Function
作为callback
所以,对于Error Boundaries
,相当于主动触发了一次更新:
this.setState(() => { // 用于执行getDerivedStateFromError的callback }, () => { // 用于执行componentDidCatch的callback // 以及 用于抛出React提示信息的callback })
- 对于根节点,执行
ReactDOM.render(element, container, callback)
中callback
参数能传递Function
作为callback
所以,对于没有Error Boundaries的情况,相当于主动执行了如下函数:
ReactDOM.render(element, container, () => { // 用于抛出“未捕获的错误”及“React的提示信息”的callback })
所以,Error Boundaries
的实现可以看作是:React
利用已有API
实现的新功能。
经常有人问:为什么Hooks
没有Error Boundaries
?
可以看到,Error Boundaries
的实现借助了this.setState
可以传递callback
的特性,useState
暂时无法完全对标。
最后,给你留个作业,在官方文档介绍了4种情况的错误不会被Error Boundaries
捕获。
利用本文知识,你能分析下他们为什么不会被捕获么?
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK