DrillLab
第 79 / 105 道79 / 105 · #333

什么是错误边界,有什么用

What are error boundaries and How are they useful

先自己答,再往下看Answer it yourself first

一句话:一个能捕获子树渲染错误的类组件, 出错时渲染兜底 UI,而不是让整个应用白屏

怎么写:实现 getDerivedStateFromError(用来切换到兜底 UI)和componentDidCatch(用来上报日志)。目前必须是 class 组件—— 没有对应的 hook。

抓不到什么(这才是考点):

  • 事件处理函数里的错误—— 因为那不在渲染过程中。 要自己 try/catch
  • 异步代码里的错误——setTimeout、 Promise 回调(和 #310 同一个道理)。
  • 服务端渲染
  • 错误边界自己抛的错。

放哪:粒度要合适。只在根节点放一个, 一个小组件挂了整页还是没了;推荐按「独立区块」放—— 每个路由页面、 每个可独立失败的挂件 (侧栏、图表、评论区)。 这样一块坏了其他还能用。

会追问:「怎么让用户能恢复?」—— 兜底 UI 里给一个「重试」按钮, 把边界的 state 重置 (或者换一个 key强制重建子树)。react-error-boundary这个库提供了 resetErrorBoundary

In one line: a class component that catches render errors in its subtree and renders a fallback UI instead of letting the whole app go blank.

How you write one: implement getDerivedStateFromError (to switch to the fallback) and componentDidCatch (to report the error). It still has to be a class component — there is no hook equivalent.

What it does not catch — this is the real question:

  • Errors inside event handlers — those do not happen during render. Use your own try/catch.
  • Errors in async codesetTimeout, Promise callbacks (the same reason as #310).
  • Server-side rendering.
  • Errors thrown by the boundary itself.

Where to put them: get the granularity right. One at the root only means a single broken widget still wipes out the page; put one around each independent block — each route, and each widget that can fail on its own (sidebar, chart, comments). Then one broken block leaves the rest usable.

Follow-up: “How does the user recover?” — give the fallback a “Retry” button that resets the boundary’s state (or changes a key to force the subtree to rebuild). The react-error-boundary library gives you resetErrorBoundary for this.

JSX错误边界示意Illustrative
1class ErrorBoundary extends React.Component {
2 state = { error: null };
3
4 static getDerivedStateFromError(error) {
5 return { error }; // 切到兜底 UI
6 }
7
8 componentDidCatch(error, info) {
9 report(error, info.componentStack); // 上报
10 }
11
12 render() {
13 if (this.state.error) {
14 return (
15 <div>
16 <p>这块出错了</p>
17 <button onClick={() => this.setState({ error: null })}>重试</button>
18 </div>
19 );
20 }
21 return this.props.children;
22 }
23}
24
25// 按区块放,而不是只在根节点放一个
26<ErrorBoundary><Sidebar /></ErrorBoundary>
27<ErrorBoundary><Chart /></ErrorBoundary>
1class ErrorBoundary extends React.Component {
2 state = { error: null };
3
4 static getDerivedStateFromError(error) {
5 return { error }; // switch to the fallback UI
6 }
7
8 componentDidCatch(error, info) {
9 report(error, info.componentStack); // report it
10 }
11
12 render() {
13 if (this.state.error) {
14 return (
15 <div>
16 <p>Something went wrong in this part</p>
17 <button onClick={() => this.setState({ error: null })}>Retry</button>
18 </div>
19 );
20 }
21 return this.props.children;
22 }
23}
24
25// Put one per region rather than a single one at the root
26<ErrorBoundary><Sidebar /></ErrorBoundary>
27<ErrorBoundary><Chart /></ErrorBoundary>