1、错误边界

部分 UI 的 JavaScript 错误不应该导致整个应用崩溃,为了解决这个问题,React 16 引入了一个新的概念 —— 错误边界。
错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

注意 错误边界无法捕获以下场景中产生的错误:

  • 事件处理(了解更多
  • 异步代码(例如 **setTimeout****requestAnimationFrame** 回调函数)
  • 服务端渲染
  • 它自身抛出来的错误(并非它的子组件)

A.错误处理组件的定义

一个 class 组件中定义了 [static getDerivedStateFromError()](https://zh-hans.reactjs.org/docs/react-component.html#static-getderivedstatefromerror)[componentDidCatch()](https://zh-hans.reactjs.org/docs/react-component.html#componentdidcatch) 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。

  1. class ErrorBoundary extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = { hasError: false };
  5. }
  6. static getDerivedStateFromError(error) {
  7. // 更新 state 使下一次渲染能够显示降级后的 UI
  8. return { hasError: true };
  9. }
  10. componentDidCatch(error, errorInfo) {
  11. // 同样可以将错误日志上报给服务器
  12. logErrorToMyService(error, errorInfo);
  13. }
  14. render() {
  15. if (this.state.hasError) {
  16. // 可以自定义降级后的 UI 并渲染
  17. return <h1>Something went wrong.</h1>;
  18. }
  19. return this.props.children;
  20. }
  21. }

B.像常规组件引入使用

  1. <ErrorBoundary>
  2. <MyWidget />
  3. </ErrorBoundary>

错误边界的工作方式类似于 JavaScript 的 catch {},不同的地方在于错误边界只针对 React 组件。只有 class 组件才可以成为错误边界组件。大多数情况下, 只需要声明一次错误边界组件, 并在整个应用中使用它。
注意:错误边界仅可以捕获其子组件的错误,它无法捕获其自身的错误。如果一个错误边界无法渲染错误信息,则错误会冒泡至最近的上层错误边界,这也类似于 JavaScript 中 catch {} 的工作机制。

C.官网示例

  1. class ErrorBoundary extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = { error: null, errorInfo: null };
  5. }
  6. componentDidCatch(error, errorInfo) {
  7. // Catch errors in any components below and re-render with error message
  8. this.setState({
  9. error: error,
  10. errorInfo: errorInfo
  11. })
  12. // You can also log error messages to an error reporting service here
  13. }
  14. render() {
  15. if (this.state.errorInfo) {
  16. // Error path
  17. return (
  18. <div>
  19. <h2>Something went wrong.</h2>
  20. <details style={{ whiteSpace: 'pre-wrap' }}>
  21. {this.state.error && this.state.error.toString()}
  22. <br />
  23. {this.state.errorInfo.componentStack}
  24. </details>
  25. </div>
  26. );
  27. }
  28. // Normally, just render children
  29. return this.props.children;
  30. }
  31. }
  32. class BuggyCounter extends React.Component {
  33. constructor(props) {
  34. super(props);
  35. this.state = { counter: 0 };
  36. this.handleClick = this.handleClick.bind(this);
  37. }
  38. handleClick() {
  39. this.setState(({counter}) => ({
  40. counter: counter + 1
  41. }));
  42. }
  43. render() {
  44. if (this.state.counter === 5) {
  45. // Simulate a JS error
  46. throw new Error('I crashed!');
  47. }
  48. return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
  49. }
  50. }
  51. function App() {
  52. return (
  53. <div>
  54. <p>
  55. <b>
  56. This is an example of error boundaries in React 16.
  57. <br /><br />
  58. Click on the numbers to increase the counters.
  59. <br />
  60. The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.
  61. </b>
  62. </p>
  63. <hr />
  64. <ErrorBoundary>
  65. <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p>
  66. <BuggyCounter />
  67. <BuggyCounter />
  68. </ErrorBoundary>
  69. <hr />
  70. <p>These two counters are each inside of their own error boundary. So if one crashes, the other is not affected.</p>
  71. <ErrorBoundary><BuggyCounter /></ErrorBoundary>
  72. <ErrorBoundary><BuggyCounter /></ErrorBoundary>
  73. </div>
  74. );
  75. }
  76. ReactDOM.render(
  77. <App />,
  78. document.getElementById('root')
  79. );

2、未捕获错误(Uncaught Errors)的新行为

自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。
把一个错误的 UI 留在那比完全移除它要更糟糕。例如,在类似 Messenger 的产品中,把一个异常的 UI 展示给用户可能会导致用户将信息错发给别人。同样,对于支付类应用而言,显示错误的金额也比不呈现任何内容更糟糕。
此变化意味着当你迁移到 React 16 时,你可能会发现一些已存在你应用中但未曾注意到的崩溃。增加错误边界能够让你在应用发生异常时提供更好的用户体验。
例如,Facebook Messenger 将侧边栏、信息面板、聊天记录以及信息输入框包装在单独的错误边界中。如果其中的某些 UI 组件崩溃,其余部分仍然能够交互。

3、组件栈追踪

React 16 会把渲染期间发生的所有错误打印到控制台,即使该应用意外的将这些错误掩盖。除了错误信息和 JavaScript 栈外,React 16 还提供了组件栈追踪。可以准确地查看发生在组件树内的错误信息:
image.png
可以在组件栈追踪中查看文件名和行号,这一功能在 Create React App 项目中默认开启
image.png
没有使用 Create React App创建React应用,可以手动将该插件添加到你的 Babel 配置中。注意它仅用于开发环境,在生产环境必须将其禁用

注意 组件名称在栈追踪中的显示依赖于 [Function.name](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name) 属性。如果想要支持尚未提供该功能的旧版浏览器和设备(例如 IE 11),考虑在打包(bundled)应用程序中包含一个 Function.name 的 polyfill,如 [function.name-polyfill](https://github.com/JamesMGreene/Function.name) 。或者,可以在所有组件上显式设置 [displayName](https://zh-hans.reactjs.org/docs/react-component.html#displayname) 属性。

4、关于 try/catch

try / catch 用于命令式代码(imperative code)

try {
  showButton();
} catch (error) {
  // ...
}

React 组件需要声明式的并且具体指出 什么 需要被渲染

<Button />

错误边界保留了 React 的声明性质。例如,即使一个错误发生在 componentDidUpdate 方法中,并且由某一个深层组件树的 setState 引起,其仍然能够冒泡到最近的错误边界。

5、关于事件处理器

错误边界无法捕获事件处理器内部的错误。
React 不需要错误边界来捕获事件处理器中的错误。与 render 方法和生命周期方法不同,事件处理器不会在渲染期间触发。因此,如果它们抛出异常,React 仍然能够知道需要在屏幕上显示什么。
如果需要在事件处理器内部捕获错误,可以使用普通的 JavaScript try / catch 语句:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    try {
      // 执行操作,如有错误则会抛出
    } catch (error) {
      this.setState({ error });
    }
  }

  render() {
    if (this.state.error) {
      return <h1>Caught an error.</h1>
    }
    return <div onClick={this.handleClick}>Click Me</div>
  }
}

6、 ErrorBoundary 处理 Reac.lazy 懒加载组件的使用

如果模块加载失败(如网络问题),它会触发一个错误。可以通过异常捕获边界(Error boundaries)技术来处理这些情况,以显示良好的用户体验并管理恢复事宜。

import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

const MyComponent = () => (
  <div>
    <MyErrorBoundary>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </MyErrorBoundary>
  </div>
);

7、React16对异常处理方法名的更改

React 15 中有一个支持有限的错误边界方法 unstable_handleError。此方法不再起作用,同时自 React 16 beta 发布起需要在代码中将其修改为 componentDidCatch