React 错误边界指南
虽然在错误到达生产环境之前捕获错误是理想的,但是其中一些错误(例如网络错误)可能会通过测试而影响用户。如果你的 React 组件没有正确地捕捉到第三方库或 React Hooks 抛出的错误,这样的错误要么导致 React 生命周期崩溃,要么到达主执行线程的顶层,导致“白屏”场景:
❝在React 16中,没有捕捉到的错误[…]将导致整个 React 组件树被卸载 ❞
image.png
您的应用程序通过提供适当的可视化反馈和潜在操作(例如:重试机制)来优雅地处理此类错误是至关重要的。
幸运的是,使用 React API 实现这样的用户体验模式只需要很少的工作,对于最高级的用户体验,还需要轻量级 React 库的帮助。
在 React Hooks 调用周围使用 JavaScript 的 try-catch
是行不通的,因为它们的执行是异步的。然而,React API 提供了错误边界机制来捕获组件中可能“冒出来”的所有类型的错误。
例如,如果 <ComponentA />
被封装在一个 React Error 边界中,错误传播将在 Error boundary 级别停止,防止 React App 崩溃:
image.png
本文将介绍如何在应用程序中实现错误边界,从简单的错误捕获到显示可视化反馈和提供重试机制。
1. 简单错误边界的捕获和报告错误
在它复杂的名字背后,Error Boundary 只是一个实 componentDidCatch(error)
方法的普通类 React 组件:
class ErrorBoundarySimple extends React.Component {
componentDidCatch(error) {
// 报告错误到您最喜欢的错误跟踪工具(例如:Sentry, Bugsnag)
}
render() {
return this.props.children;
}
}
注意:React 还没有提供基于 hook 的替代方法来实现错误边界。
一旦错误到达我们的 MyErrorBoundary
组件,componentDidCatch()
类方法就会被调用,这允许我们防止 React 应用程序崩溃并将错误转发到我们的错误报告工具。
让我们让 <ErrorBoundarySimple>
更加友好,在错误被抛出时添加简单的可视化反馈。为此,我们向 ErrorBoundarySimple
添加一些状态,并使用 getDerivedStateFromError()
方法,如下所示:
class ErrorBoundarySimple extends React.Component {
state = { hasError: false };
componentDidCatch(error: unknown) {
// 报告错误到您最喜欢的错误跟踪工具(例如:Sentry, Bugsnag)
console.error(error);
}
static getDerivedStateFromError(error: unknown) {
// 更新状态,使下一次渲染将显示回退 UI
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <p>Failed to fetch users.</p>;
}
return this.props.children;
}
}
React 期望 getDerivedStateFromError()
方法在发生错误时返回应用于 <ErrorBoundarySimple>
的状态值,从而使我们的 UI 提供视觉反馈。
错误边界也可以嵌套,以提供更多上下文化的反馈。例如,在这个 React 应用树中,我们可能想根据崩溃的内容提供不同的反馈。例如,当聊天崩溃和 TodoList
崩溃时,我们可能希望提供不同的反馈,但仍然在应用程序级别处理任何类型的崩溃。我们可以引入多个边界来实现这一点:
image.png
通过上面的设置,<Chat>
组件(或它的子组件)中的任何错误都将被捕获在包装 <Chat>
组件的错误边界(而不是“App”错误边界)中,允许我们给出上下文化的可视化反馈。但是,来自所有 <App>
后代的任何错误(不包括<Chat>
和 <TodoList>
)将被" App "错误边界捕获。
仅用几行代码,我们就通过优雅地处理应用程序中的错误,极大地改善了用户体验。然而,这种简单的错误边界实现确实有局限性。
首先,根据 React 文档,错误边界不会捕获以下错误:
- 事件处理
- 异步代码(例如
setTimeout
或requestAnimationFrame
回调) - 服务器端渲染
- 抛出在错误边界本身(而不是其子边界)中的错误
而且,前面展示的错误边界没有为用户提供从错误中恢复的任何操作,例如,通过重试机制。在下一节中,我们将了解如何利用 react-error-boundary
库来处理所有这些边界情况。
2. 高级错误边界的捕获所有错误和重试机制
现在,让我们通过捕捉各种错误并向用户公开恢复操作来提供高级的错误处理用户体验。为此,我们将使用 react-error-boundary
库,该库可以按如下方式安装:
npm install --save react-error-boundary
yarn add react-error-boundary
2.1 「提供重试机制」
我们新定义了一个 <Users>
组件,该组件在50%的情况下无法加载用户。
让我们使用 react-error-boundary
来正确捕获错误并提供重试机制:
import { ErrorBoundary, FallbackProps } from "react-error-boundary";
import { Users } from "./Users";
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div role="alert">
<p>Failed to load users:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
export default function App(): JSX.Element {
return (
<div className="App">
<ErrorBoundary FallbackComponent={ErrorFallback}>
{/* Users 加载失败的概率为50% */}
<Users />
</ErrorBoundary>
</div>
);
}
<ErrorBoundary>
接受一个强制的 FallbackComponent = prop
,它应该是发生错误时将呈现的 react 组件或 JSX。如果是一个组件,这个FallbackComponent=function
将接收 FallbackProps
:
error
可用于显示错误。resetErrorBoundary
是一个回调函数,用于重置错误状态并重新渲染子组件。
还可以提供 ononError
prop,将错误转发到您最喜欢的错误报告工具(例如:Sentry)。react-error-boundary
「文档」 展示了如何利用其他props(例如:onReset=
)来处理更高级的场景。
2.2 捕获所有的错误
如前所述,错误边界不会捕获以下错误:
- 事件处理
- 异步代码(例如
setTimeout
或requestAnimationFrame
回调)
因为这种错误发生在 React 呈现生命周期之外,所以不会调用 错误边界。同样,通过提供 handleError()
hook 来帮助捕获与事件相关的和异步错误,庆幸的是 react-error-boundary
已经给我们提供了。
import { useErrorHandler } from 'react-error-boundary'
function Greeting() {
const [greeting, setGreeting] = React.useState(null)
const handleError = useErrorHandler()
function handleSubmit(event) {
event.preventDefault()
const name = event.target.elements.name.value
fetchGreeting(name).then(
newGreeting => setGreeting(newGreeting),
error => handleError(error),
)
}
return greeting ? (
<div>{greeting}</div>
) : (
<form onSubmit={handleSubmit}>
<label>Name</label>
<input id="name" />
<button type="submit">get a greeting</button>
</form>
)
}
在 handleSubmit()
函数中发生的错误不会被 React 呈现生命周期捕获。因此,我们使用 React -error-boundary
的 useErrorHandler()
提供的 handleError
函数在 React 生命周期中重新抛出错误,以便最近的 ErrorBoundary
可以捕获它。
3. 小结
React Error Boundary 是一种优雅地处理 React 应用程序中任何类型错误的直接方法。
好的产品应该防止错误到达生产,但也应该使用错误边界为用户提供上下文反馈和恢复操作,以防出现意外错误。