十八、错误边界


这是从 React 16 引入了一个新的概念,为了解决的问题就是一个组件出错不至于整个应用崩溃。

首先我们还是来看一个示例:

import React from 'react';
import ChildCom1 from "./components/ChildCom1"
import ChildCom2 from "./components/ChildCom2"

function App() {
  return (
    <div>
      <ChildCom1/>
      <ChildCom2/>
    </div>
  );
}

export default App;
import ChildCom3 from "./ChildCom3";
function ChildCom1() {
  return (
    <div style={{
      width: "300px",
      height: "300px",
      border: "1px solid"
    }}>
      ChildCom1
      <ChildCom3 />
    </div>
  );
}

export default ChildCom1;
function getData() {
  return;
}
function ChildCom3() {
  const arr = getData();
  const spanContent = arr.map(it => <span>{it}</span>)
  return (
    <div style={{
      width: "100px",
      height: "100px",
      border: "1px solid"
    }}>
      ChildCom3
      <div>{spanContent}</div>
    </div>
  );
}

export default ChildCom3;
function ChildCom2() {
  return (
    <div style={{
      width: "300px",
      height: "300px",
      border: "1px solid"
    }}>ChildCom2</div>
  );
}

export default ChildCom2;

在上面的代码中,我们整个组件树结构如下:


可以看到,ChildCom1 下面的 ChildCom3 存在问题,这一个组件的问题会导致整个应用崩溃。

这在某些场景下,实际上是没有必要的,例如有问题的组件是广告、或者一些无关紧要的组件,此时我们就期望渲染出问题组件以外的组件树。

错误边界就是用来解决这个问题的。

错误边界是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI ,而并不会渲染那些发生崩溃的子组件树。错误边界可以捕获发生在整个子组件树的渲染期间、生命周期方法以及构造函数中的错误。

// components/ErrorBoundary.jsx

import React, { Component } from 'react'

export default class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    console.log("error>>>", error)
    console.log("errorInfo>>>", errorInfo)
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

在上面的代码中,我们就创建了一个错误边界组件,该组件有一个 getDerivedStateFromError 静态方法以及 componentDidCatch 实例方法,这两个方法都会在组件渲染出错时调用,但是略有区别,具体的区别如下:

  • getDerivedStateFromError 静态方法
    • 运行时间点:渲染子组件的过程中,发生错误之后,在更新页面之前(整个应用没有崩溃,直接渲染降级 UI)
    • 注意:只有子组件发生错误,才会运行该函数
    • 该函数返回一个对象,React 会将该对象的属性覆盖掉当前组件的 state
    • 参数:错误对象
    • 通常,该函数用于改变状态
  • componentDidCatch 实例方法
    • 运行时间点:渲染子组件的过程中,发生错误,更新页面之后(整个应用已经崩溃了,getDerivedStateFromError 执行降级UI,页面重新渲染),由于其运行时间点比较靠后,因此不太会在该函数中改变状态
    • 通常,该函数用于记录错误消息

最佳实践,使用 static getDerivedStateFromError 渲染备用 UI ,使用 componentDidCatch 打印错误信息。

之后,我们就使用这个错误边界组件来包裹要忽略渲染错误的子组件,例如:

import ChildCom3 from "./ChildCom3";
import ErrorBoundary from "./ErrorBoundary"

function ChildCom1() {
  return (
    <div style={{
      width: "300px",
      height: "300px",
      border: "1px solid"
    }}>
      ChildCom1
      <ErrorBoundary>
        <ChildCom3 />
      </ErrorBoundary>
    </div>
  );
}

export default ChildCom1;

有了错误边界组件后,ChildCom3 组件中的渲染错误并不会导致整个应用崩溃。效果如下:


最后需要注意的是,错误边界组件主要是用来捕获 UI 渲染时的错误,因此如下场景中错误是无法捕获的:

  • 事件处理
  • 异步代码
  • 服务端渲染
  • 它自身抛出来的错误

总之,错误边界组件仅能过处理渲染子组件期间的同步错误。


文章作者: 吴俊杰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吴俊杰 !
  目录