使用错误边界处理错误

Avatar of Kingsley Silas
Kingsley Silas

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费积分!

在 React 中思考和构建涉及以块或组件的形式进行应用程序设计。 您的应用程序中执行操作的每个部分都可以并且应该被视为一个组件。 实际上,React 是基于组件的,并且,正如 Tomas Eglinkas 最近写道,我们应该利用这个概念,并在将任何大的块拆分为更小的组件方面犯错。

拆分不可避免地会引入组件层次结构,这很好,因为它们使组件和体系结构膨胀。 但是,当子组件中出现错误时,事情可能会变得复杂。 当整个应用程序崩溃时会发生什么?!说真的,React,为什么父组件和兄弟组件必须为另一个组件的罪过付出代价?为什么?

错误边界

React 16 带来了很多好东西,其中之一是 错误边界。 让我们查阅文档并分解它对这个宝石的描述,因为我们可以用它来发现错误发生的位置,并更快地解决它们,而且头痛更少!

错误边界是 React 组件,它们捕获其子组件树中任何位置的 JavaScript 错误,记录这些错误,并显示一个回退 UI 而不是崩溃的组件树。 错误边界捕获渲染期间、生命周期方法中以及它们下方整个树的构造函数中的错误。

这有很多行话,但就像组件一样,我们可以将其分解为更简单的块。

错误边界是 React 组件

这很有道理也很有用,因为它是我们一直使用的概念。 区别在于它被喷洒了果汁,使其不同于普通组件。 不过,不要忘记错误边界本身就是 React 组件的基本思想!

错误边界捕获其子组件树中任何位置的 JavaScript 错误

如果您忘记了子组件树是如何工作的,这里有一个示例

<ParentComponent>
  <FirstChild>
    <FirstChildDaughter>
    </FirstChildDaughter>
  </FirstChild>
  <SecondChild>
  </SecondChild>
</ParentComponent>

我们有两个父组件和三个子组件。 根据我们迄今为止关于错误边界的了解,我们可以复制上面的树到

<ErrorBoundaryComponent>
  <ParentComponent>
    <FirstChild>
      <FirstChildDaughter>
      </FirstChildDaughter>
    </FirstChild>
    <SecondChild>
    </SecondChild>
  </ParentComponent>
</ErrorBoundaryComponent>

通过将整棵树包装在 ErrorBoundaryComponent 中,我们可以捕获其子组件中发生的任何 JavaScript 错误。 很酷,对吧?

错误边界记录这些错误

当错误被捕获时,我们希望边界错误对它们做一些事情,最好是告诉我们。 开发人员经常使用错误日志记录平台来监控其软件中出现的错误。 使用错误边界,我们可以做同样的事情。

错误边界显示一个回退 UI

与其显示整个恼人的红色组合,您可以选择一个自定义的用户界面,以便在出现错误时显示。 这可能非常方便,因为它允许您以一种更易于阅读和扫描的风格来定制错误。 很酷,对吧?

就像我一样,您会认为这意味着错误边界将捕获所有 JavaScript 错误。 遗憾的是,事实并非如此。 以下是它们将 优雅地忽略 的错误

  • 事件处理程序
  • 异步代码(例如 setTimeoutrequestAnimationFrame 回调)
  • 服务器端渲染
  • 在错误边界本身(而不是其子级)中抛出的错误

componentDidCatch()

使组件成为错误边界的额外果汁是 componentDidCatch() - 这是一个生命周期方法,它就像 JavaScript catch{} 块,但适用于组件。 当在子组件中发现错误时,错误将由最接近的错误边界处理。 只有类组件可以作为错误边界。

componentDidCatch() 接受两个参数

  • error:这是抛出的错误
  • info:一个包含错误发生位置跟踪的对象

错误边界在行动

假设我们正在开发一个功能,该功能列出可以举办会议的地点。 就像这样

查看 CodePen 上 Kingsley Silas Chijioke (@kinsomicrote) 创建的 错误边界 0

该应用程序从 Location 组件中列出地点,各个地点以 Location Cards 的形式输出。 我们额外注意确保每个地点的名称以大写形式渲染,以保持一致性。 出于本教程的目的,我们将在地点列表中添加一个空对象。

class Location extends React.Component {
  state = {
    locations: [
      {
        "name": "Ojo",
        "zone": "Lagos State",
        "region": "South West"
      },
      {
        "name": "Ahiazu Mbaise",
        "zone": "Imo State",
        "region": "South East"
      },
      {
        "name": "Akoko-Edo",
        "zone": "Edo State",
        "region": "South South"
      },
      {
        "name": "Anka",
        "zone": "Zamfara State",
        "region": "North West"
      },
      {
        "name": "Akwanga",
        "zone": "Nasarawa State",
        "region": "North Central"
      },
      {
        
      }
    ]
  }
  render() {
    return (
      <div>
        <div>
          <div>
            <h2>Locations</h2>
          </div>
        </div>
        <div>
          {this.state.locations
            .map(location => 
              <LocationCard key={location.id} {...location} />
          )}
        </div>
      </div>
    )
  }
}

const LocationCard = (props) => {
  return (
    <div>
      <hr />
      <p><b>Name:</b> {props.name.toUpperCase()}</p>
      <p><b>Zone:</b> {props.zone}</p>
      <p><b>Region:</b> {props.region}</p>
      <hr />
    </div>
  )
}

const App = () => (
  <div>
     <Location />
  </div>
)

ReactDOM.render(<App />, document.getElementById("root"));

如果您在浏览器中运行它,您将看到类似于此屏幕截图的错误

A screenshot of the Type Error providing the error message Cannot read property toUpperCase of undefined. Background color is tan and there is a block of code with a light red background indicating where the error is in the code base.

这并不完全有用,所以让我们应用一个错误边界来帮助我们。 首先,我们将创建一个 ErrorBoundary 组件

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      info: null
    };
  }
  componentDidCatch(error, info) {
    this.setState({
      hasError: true,
      error: error,
      info: info
    });
  }
  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h1>Oops, something went wrong :(</h1>
          <p>The error: {this.state.error.toString()}</p>
          <p>Where it occured: {this.state.info.componentStack}</p>
        </div>
      );
    }
    return this.props.children;
  }
}

创建 hasErrorerrorinfo 的初始状态。 然后,添加 componentDidCatch() 生命周期方法。 如果在任何子组件的构造函数、渲染或生命周期方法中发生错误,则 hasError 状态将更改为 true。 当发生这种情况时,ErrorBoundary 组件将渲染并显示错误。 但是,如果没有错误,则会渲染 ErrorBoundary 组件的子级,正如我们所期望的那样。

接下来,我们需要将 ErrorBoundaryLocation 组件都添加到我们主要的 App 组件中

const App = () => (
  <div>
    <ErrorBoundary>
      <Location />
    </ErrorBoundary>
  </div>
)

查看 CodePen 上 Kingsley Silas Chijioke (@kinsomicrote) 创建的 错误边界 2

我们不再看到那个烦人的 TypeError UI! 事情正在起作用!

我们可以做一件事来改进应用程序。 如果您检查演示中的代码,您会看到我们在末尾添加了一个空对象。 是否可以渲染其他可靠的地点? 当然可以! 在 Location 组件内部,我们可以用 ErrorBoundary 组件包装 LocationCard 组件,以将错误日志记录直接限定到卡片

class Location extends React.Component {
  state = {
    locations: [
      {
        "name": "Ojo",
        "zone": "Lagos State",
        "region": "South West"
      },
      {
        "name": "Ahiazu Mbaise",
        "zone": "Imo State",
        "region": "South East"
      },
      {
        "name": "Akoko-Edo",
        "zone": "Edo State",
        "region": "South South"
      },
      {
        "name": "Anka",
        "zone": "Zamfara State",
        "region": "North West"
      },
      {
        "name": "Akwanga",
        "zone": "Nasarawa State",
        "region": "North Central"
      },
      {
        // Empty!
      }
    ]
  }
  render() {
    return (
      <div>
        <div>
          <div>
            <h2>Locations</h2>
          </div>
        </div>
        <div>
          {this.state.locations
            .map(location => 
            <ErrorBoundary>
              // Should render all locations, but the empty instance
              <LocationCard key={location.id} {...location} />
            </ErrorBoundary>
          )}
        </div>
      </div>
    )
  }
}

这次,可靠的地点显示了,除了空的那个。 您可以选择用错误边界组件包装整个组件树一次,或者您可以在策略性位置包装不同的组件。 决定权在你手中

总结

我鼓励您开始在您的应用程序中使用错误边界。 同样,值得深入挖掘。 为此,以下是 React 存储库中关于错误边界和事件处理程序的一些问题,请查看它们,以便您了解事物的当前状态