为什么Hook没有ErrorBoundary?

开发 前端
ErrorBoundary​在ClassComponent​中的实现使用了this.setState​的回调函数特性,这使得Hooks中要完全实现同样功能,需要额外开发成本。

大家好,我卡颂。

在很多全面使用Hooks开发的团队,唯一使用ClassComponent的场景就是「使用ClassComponent创建ErrorBoundary」。

可以说,如果Hooks存在如下两个生命周期函数的替代品,就能全面抛弃ClassComponent了:

  • getDerivedStateFromError
  • componentDidCatch

那为什么还没有对标的Hook呢?

今天我们从上述两个生命周期函数的实现原理,以及要移植到Hook上需要付出的成本来谈论这个问题。

ErrorBoundary实现原理

ErrorBoundary可以捕获子孙组件中「React工作流程」内的错误。

「React工作流程」指:

  • render阶段,即「组件render」、「Diff算法」发生的阶段。
  • commit阶段,即「渲染DOM」、「componentDidMount/Update执行」的阶段。

这也是为什么「事件回调中发生的错误」无法被ErrorBoundary捕获 —— 事件回调并不属于「React工作流程」。

如何捕获错误

「render阶段」的整体执行流程如下:

do {
try {
// render阶段具体的执行流程
workLoop();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);

可以发现,如果「render阶段」发生错误,会被捕获并执行handleError方法。

类似的,「commit阶段」的整体执行流程如下:

try {
// ...具体执行流程
} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
}

如果「commit阶段」发生错误,会被捕获并执行captureCommitPhaseError方法。getDerivedStateFromError原理。

捕获后的错误如何处理呢?

我们知道,ClassComponent中this.setState第一个参数,除了可以接收「新的状态」,也能接收「改变状态的函数」作为参数:

// 可以这样
this.setState(this.state.num + 1)
// 也可以这样
this.setState(num => num + 1)

getDerivedStateFromError的实现,就借助了this.setState中「改变状态的函数」这一特性。

当捕获错误后,即:

  • 对于「render阶段」,handleError执行后。
  • 对于「commit阶段」,captureCommitPhaseError执行后。

会在ErrorBoundary对应组件中触发类似如下更新:

this.setState(
getDerivedStateFromError.bind(null, error)
)

这就是为什么getDerivedStateFromError要求开发者返回「新的state」— 本质来说,他就是触发一次新的更新。

componentDidCatch原理

再来看另一个ErrorBoundary相关的生命周期函数— componentDidCatch。

ClassComponent中this.setState的第二个参数,可以接收「回调函数」作为参数:

this.setState(newState, () => {
// ...回调
})

当触发的更新渲染到页面后,回调会触发。

这就是componentDidCatch的实现原理。

当捕获错误后,会在ErrorBoundary对应组件中触发类似如下更新:

this.setState(this.state, componentDidCatch.bind(this, error))

处理“未捕获”的错误

可以发现,「React运行流程」中的错误,都已经被React自身捕获了,再交由ErrorBoundary处理。

如果没有定义ErrorBoundary,这些「被捕获的错误」需要重新抛出,营造「错误未被捕获的感觉」。

那这一步在哪里执行呢?

与this.setState类似,ReactDOM.render(element, container[, callback])第三个参数也能接收「回调函数」。

如果开发者没有定义ErrorBoundary,那么React最终会在ReactDOM.render的回调中抛出错误。

可以发现,在ClassComponent中ErrorBoundary的实现完全依赖了ClassComponent已有的特性。

而Hooks本身并不存在类似this.setState的回调特性,所以实现起来会比较复杂。

实现Hooks中的ErrorBoundary

除了上述谈到的阻碍,FunctionComponent与ClassComponent在源码层面的运行流程也有细节上的差异,要照搬实现也有一定难度。

如果一定要实现,在「最大程度复用现有基础设施」的指导方针下,useErrorBoundary(ErrorBoundary在Hooks中的实现)的使用方式应该类似如下:

function ErrorBoundary({children}: {children: ReactNode}) {
const [errorMsg, updateError] = useState<Error | null>(null);
useErrorBoundary((e: Error) => {
// 捕获到错误,触发更新
updateError(e);
})
return (
<div>
{errorMsg ? '报错:' + errorMsg.toString() : children}
</div>
)
}

其中useErrorBoundary的触发方式类似useEffect:

useErrorBoundary((e: Error) => {
// ...
})
// 类似
useEffect(() => {
// ...
})

笔者仿照ClassComponent中ErrorBoundary的实现原理与useEffect的实现原理,实现了原生Hooks — useErrorBoundary。

感兴趣的朋友可以在useErrorBoundary在线示例[1]体验效果。

总结

ErrorBoundary在ClassComponent中的实现使用了this.setState的回调函数特性,这使得Hooks中要完全实现同样功能,需要额外开发成本。

笔者猜测,这是没有提供对应原生Hooks的原因之一。

参考资料

[1]useErrorBoundary在线示例:https://codesandbox.io/s/angry-mountain-xstgj4?file=/src/App.js。

责任编辑:姜华 来源: 魔术师卡颂
相关推荐

2020-07-22 07:55:12

Python开发函数

2013-03-18 09:30:18

Lisp

2022-05-22 21:23:10

前端监控系统

2022-07-13 15:23:57

Vue fiberreact前端

2015-11-30 19:01:38

乐视生态

2020-06-01 21:24:50

物联网应用程序IOT

2020-08-23 11:03:24

Python开发void

2018-03-21 10:00:14

混合云爆发发生

2024-01-03 07:50:09

云计算数据中心ITSM

2021-02-01 10:10:55

Svelte框架开发

2021-07-21 09:35:36

switchbreakJava

2016-10-24 14:35:04

2012-08-07 16:46:35

苹果Apple TV

2017-07-03 10:52:20

深度学习人工智能

2015-04-23 10:52:53

AndroidiOS图片

2023-06-16 15:52:19

云计算数据中心

2023-09-01 16:17:53

API人工智能

2022-08-17 14:35:27

智慧城市物联网社区

2019-04-03 15:46:55

中国编程语言

2015-04-23 10:15:53

AndroidiOS图片
点赞
收藏

51CTO技术栈公众号