## 1 引言

Error Boundaries 是 React16 提出来用来捕获渲染时错误的概念，今天我们一起读一读 [A Simple Guide to Error Boundaries in React](https://alligator.io/react/error-boundaries/) 这篇文章，了解一下这个重要机制。

## 2 概述

Error Boundaries 可以用来捕获渲染时错误，API 如下：

```jsx
class MyErrorBoundary extends Component {
  state = {
    error: null,
  };

  static getDerivedStateFromError(error) {
    // 更新 state，下次渲染可以展示错误相关的 UI
    return { error: error };
  }

  componentDidCatch(error, info) {
    // 错误上报
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.error) {
      // 渲染出错时的 UI
      return <p>Something broke</p>;
    }
    return this.props.children;
  }
}
```

- `static getDerivedStateFromError`: 在出错后有机会修改 state 触发最后一次错误 fallback 的渲染。
- `componentDidCatch`: 用于出错时副作用代码，比如错误上报等。

这两种方法中任意一个被定义时，这个组件就会成为 `Error Boundary` 组件，可以阻止子组件渲染时报错。

最后作者还提出一个建议，建议将 Error Boundary 单独作为一个组件，而不是将错误监听方法与业务组件耦合，一方面考虑到复用，另一方面则因为错误检测只对子组件生效。

好吧，其实 React 官方文档比这篇文章介绍的详细的多得多，原文介绍到此结束。

## 3 精读

[React Error Boundaries 官方文档](https://reactjs.org/docs/error-boundaries.html) 里提到了四种无法 Catch 的错误场景：

1. 回调事件。由于回调事件执行时机不在渲染周期内，因此无法被 Error Boundary Catch 住，如有必要得自行 try/catch。
2. 异步。比如 `setTimeout` 或 `requestAnimationFrame`，和第一条同理。
3. 服务端渲染。
4. Error Boundary 组件自身触发的错误。因为只能捕获其子组件的错误。

这也是使用 Error Boundaries 最容易有疑问的地方。除了上面的情况，笔者结合自身经验再列举几种异常边界场景。

### 无法捕获编译时错误

很明显，即便是 React 官方 API `Error Boundary` 也只能捕获运行时错误，而对编译时错误无能为力。

编译时错误包括不限于编译环境错误、运行前的框架错误检查提示、TS/Flow 类型错误等，这些都是 `Error Boundary` 无法捕获的，而且没有更好的办法 Catch 住，遇到编译错误就在编译时解决吧，仅关注运行时错误就好了。

### 可以作用于 Function Component

虽然函数式组件无法定义 `Error Boundary`，但 `Error Boundary` 可以捕获函数式组件的错误，因此可以曲线救国：

```jsx
// ErrorBoundary 组件
class ErrorBoundary extends React.Component {
  // ...
}

// 可以捕获所有组件异常，包括 Function Component 的子组件
const App = () => {
  return (
    <ErrorBoundary>
      <Child />
    </ErrorBoundary>
  );
};
```

### 对 Hooks 也可生效

对于 Hooks 中异常也可以生效，比如下面的代码：

```jsx
const Child = (props) => {
  React.useEffect(() => {
    console.log(1);
    props.a.b;
    console.log(2);
  }, [props.a.b]);

  return <div />;
};
```

要注意的是，出现在 deps 中的错误会立即被 Catch，导致 `console.log(1)` 都无法打印。但如果是下面的代码，则可以打印出 `console.log(1)`，无法打印出 `console.log(2)`:

```jsx
const Child = (props) => {
  React.useEffect(() => {
    console.log(1);
    props.a.b;
    console.log(2);
  }, []);

  return <div />;
};
```

所以 React 官网的这句话并不是指 `Error Boundary` 对 Hooks 不生效，而是指 `Error Boundary` 无法以 Hooks 方式指定，对功能是没有影响的：

> componentDidCatch and getDerivedStateFromError: There are no Hook equivalents for these methods yet, but they will be added soon.

所以这里的理解要注意一下，另外 React 官方文档 [Hooks FAQ](https://reactjs.org/docs/hooks-faq.html#how-do-lifecycle-methods-correspond-to-hooks) 有很多宝藏，建议抽时间逐条阅读。

## 4 总结

`Error Boundary` 可以捕获所有子元素渲染时异常，包括 render、各生命周期函数，但也有很多使用限制，希望你可以正确使用它。

错误捕获也不是万能的，更多时候我们要避免并及时修复错误，通过错误捕获降低出错时对用户体验的影响，并在第一时间内监控起来并快速修复。

最后，你有明明正确使用了 `Error Boundary` 却依然无法 Catch 住的错误 Case 吗？

> 讨论地址是：[精读《React Error Boundaries》 · Issue #246 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/246)

**如果你想参与讨论，请 [点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**

> 关注 **前端精读微信公众号**

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

> 版权声明：自由转载-非商用-非衍生-保持署名（[创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)）
