概念
ErrorBoundrry是react提出的错误边界捕获的概念,为了解决在渲染等时机中发生错误导致的页面白屏问题,我们可以在ErrorBoundrry提供降级UI,提升用户体验。
它有两个生命周期函数:
static getDerivedStateFromError负责在捕获错误时返回新的状态,触发备用UI的渲染。componentDidCatchcomponentDidCatch则是状态更新成功后的钩子,可以作为错误上报的时机。
使用
1.ErrorBoundrry实现:
根据上面的概念,我们得知实现ErrorBoundrry主要就是依赖
getDerivedStateFromError和componentDidCatch配合使用。getDerivedStateFromError负责在错误捕获后更新ErrorBoundary组件的状态,componentDidCatch是降级ui渲染后的钩子,主要用来做错误打印与上报
import React from 'react'
/**
* 错误边界捕获
*/
export default class ErrorBoundary extends React.Component {
state = { hasError: false }
componentDidCatch(error) {
console.error('[错误边界捕获] 错误信息:', error)
// 上报BoundaryError
reportBoundaryError(error)
}
static getDerivedStateFromError(error) {
// 更新 state
return { hasError: true }
}
render() {
if (this.state.hasError) {
// 降级后的 UI
return (
this.props.errorElement
)
}
return this.props.children
}
}
- ErrorBoundrry使用:
包裹在需要降级UI的组件外,通常是根节点,方便捕获整个应用在render期间发生的所有错误
<ErrorBoundrry>
<Root />
</ErrorBoundrry>
原理
- ErrorBoundrry能捕获哪些时机的异常?
- render阶段,即组件render、Diff算法发生的阶段
- commit阶段,即渲染DOM、componentDidMount/Update执行的阶段
// render阶段
function renderRootSync(root: FiberRoot, lanes: Lanes) {
...
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
// 处理错误
handleError(root, thrownValue);
}
} while (true);
...
}
// commit阶段
function commitRootImpl(root, renderPriorityLevel) {
try {
commitBeforeMutationEffects();
} catch (error) {
// 处理错误
captureCommitPhaseError(nextEffect, error);
}
}
getDerivedStateFromError的执行过程
发生错误被捕获后,getDerivedStateFromError,将其返回结果入队列更新fiber,相当于一次setState
function createClassErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
lane: Lane,
): Update<mixed> {
...
// 获取getDerivedStateFromError后执行并将结果返回
const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
if (typeof getDerivedStateFromError === 'function') {
const error = errorInfo.value;
update.payload = () => {
logCapturedError(fiber, errorInfo);
return getDerivedStateFromError(error);
};
}
...
}
// 将getDerivedStateFromError执行结果入队列更新fiber,相当于一次setState
const update = createClassErrorUpdate(
fiber,
errorInfo,
(SyncLane: Lane),
);
enqueueUpdate(fiber, update);
3.componentDidCatch是ErrorBoundary组件更新后执行的钩子
// This component has already been unmounted.
// We can't schedule any follow up work for the root because the fiber is already unmounted,
// but we can still call the log-only boundary so the error isn't swallowed.
//
// TODO This is only a temporary bandaid for the old reconciler fork.
// We can delete this special case once the new fork is merged.
if (
typeof instance.componentDidCatch === 'function' &&
!isAlreadyFailedLegacyErrorBoundary(instance)
) {
try {
instance.componentDidCatch(error, errorInfo);
} catch (errorToIgnore) {
// TODO Ignore this error? Rethrow it?
// This is kind of an edge case.
}
}