[译]React高级话题之Error Boundaries

前言

本文为意译,翻译过程当中掺杂本人的理解,若有误导,请放弃继续阅读。javascript

原文地址:Error Boundarieshtml

在过去,React组件内部的javascript错误每每会让React内部的state变得不可用,而且会在下一次的渲染过程当中产生模棱两可的错误信息。这些错误经常是由APP先前的错误所引发的,可是React并无提供一个优雅的方案去在组件内部处理这种错误,并将APP恢复到正常的状态。java

正文

介绍Error Boundaries

应用中局部UI中的javascript错误按理说不该该致使整个应用的崩溃。为了帮助React用户解决这种问题,React在16.x.x中引入了新的概念-“error boundary”。react

什么是“error boundary”呢?“error boundary”就是一种可以捕获它的子组件树所产生的错误的React组件。在这种组件里,你可以把这些错误日志打印出来,又或者相比简单粗暴地把组件树崩溃后的界面呈现给用户,你能够呈现一个精心设计过的备用界面给用户(为了强调error boundary是一个组件,我后面的翻译过程当中使用<Error boundary>来指代)。<Error boundary>能捕获在渲染过程当中,全部子组件的constructors和生命周期函数里面发生的错误。git

<Error boundary>不能捕获如下类型的错误:github

  • 发生在事件处理器里面的。
  • 异步代码。例如 setTimeout,或者requestAnimationFrame的callbacks。
  • 服务端渲染
  • <Error boundary>自己抛出的错误。

从代码的层面来讲,只要一个class component定义了static getDerivedStateFromError()componentDidCatch()方法中的一个,又或者两个都定义了,咱们就说它是一个<Error boundary>。上面提到的两个方法实际上是有分工的。通常来讲,static getDerivedStateFromError()是不容许发生反作用的,故是负责呈现一个备用的UI给用户。componentDidCatch()容许发生反作用,故负责打印错误日志,发送错误日志到远程服务器等。以下:npm

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

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}
复制代码

而后呢,你能够把它当作一个普通的组件来用:浏览器

<ErrorBoundary>
  <MyWidget /> </ErrorBoundary>
复制代码

<Error boundary>使用起来就像catch {}语句,只不过它是用于React component而已。只有class component才能成为<Error boundary>。在实际应用中,大多数状况,你只须要定义一个<Error boundary>,而后处处使用它。服务器

注意,<Error boundary>是不能捕获本身所产生的错误,只能捕获在它之下的组件树所产生的错误。在<Error boundary>嵌套使用的状况下,若是某个<Error boundary>不能渲染一些错误信息(调用static getDerivedStateFromError()失败?),那么这个错误就会往上冒泡到层级最近的个<Error boundary>。这也是catch{}语句在javascript里面的执行机制。babel

在线Demo

查看如何在React 16版本中定义和使用 error boundary

在哪里“放置”Error Boundaries

在组件树中“放置”<Error boundary>的粒度彻底取决于你。你能够把你最顶级的route component包裹在<Error boundary>中,而后让<Error boundary>呈现一个备用的界面给用户,例如“Something went wrong”。服务端渲染常常就是这样应对应用崩溃的。你也能够把多个组件分开包裹在<Error boundary>中,以此隔离局部UI之间的影响。

错误捕获的新行为

在React 16版本中,任何没有被<Error boundary>捕获的错误都会致使整一颗React组件树的卸载。这么作,是有咱们本身的考量的。

关于这个决定,咱们是有争论过的。可是,依据咱们以往的经验来看,遗留一个不正常(corrupted)的页面给用户比彻底不显示更糟糕。举个例子,在Messenger这种的产品中,把一个不正常的页面给用户会致使信息错发给别的人。一样的,对于一些涉及到支付的应用,状况会更糟糕。由于涉及到钱的问题都是大问题啊,因此说宁愿什么都不显示,也不要显示一个错误的金额数字给用户。

这种错误捕获的新行为对你是有影响的。假如你已经迁移到React 16版本上面来,你应该去检查一下你的应用,看看哪些地方有可能致使应用崩溃。而后,在对应的地方添加<Error boundary>,经过备用UI界面来提供一个更好的用户体验。

咱们来看看,Facebook的Messenger是怎么作的。Messenger分别将sidebar的内容,info panel,conversation log和 message input等区域包裹在不用的<Error boundary>中去。若是这些区域中的某个组件发生了错误,那么影响范围也就仅仅限定在这个区域中而已,别的区域将不会受到影响的。

使用<Error boundary>的同时,咱们也鼓励你去使用js错误报告服务(或本身搭建一个服务器),好让你能在第一时间了解到在生产环境所产生的未处理异常,并及时修复它。

组件栈的追踪

在开发环境下,React 16会将渲染过程当中出现的全部错误打印在控制台。这也包括了那些应用程序静默处理的错误(even if the application accidentally swallows them.)。除了错误信息和js的调用栈,React 16还会打印组件树的栈追踪。如今,你能够看到错误是发生在组件树中的哪一个位置了。

在组件栈追踪里面,你也以可查看错误组件代码所在的文件和行号。在由create-react-app建立的项目里面,这是默认行为:

若是你没有使用create-react-app来建立你的应用脚手架,那么你也能够经过给你的Babel手动地添加这个插件来实现这个功能。注意,这个功能只应该在开发环境使用,假如你是经过给Babel配置来实现这个功能的话,那么你记得在生产环境下禁用它。

注意:在组件追踪栈上显示的组件名是基于Function.name属性的。假如你须要支持一些没有在原生层级实现了这个特性的老浏览器或者设备(例如:IE11),那么就能够考虑引用polyfill(function.name-polyfill)到你的代码中。除此以外,你也能够显式地指定组件的displayName属性的值。

那try/catch怎么办啊?

try / catch 是挺好用的,可是也仅仅针对命令式代码好用而已:

try {
  showButton();
} catch (error) {
  // ...
}
复制代码

然而,React组件是经过声明式的编码来指定咱们想要渲染什么:

<Button />
复制代码

<Error boundary>保留了React天生的声明式特性,使用它的结果也会如你所愿的。举个例子说,在componentDidUpdate方法里面发生了一个错误,即便这个错误是由层级很深的组件在调用setState的时候引发的,这个错误仍是会正确地冒泡到离它最近的那个<Error boundary>中去,并被它所捕获。

那Event Handlers怎么办啊?

<Error boundary>没法捕获发生在事件处理器中的错误。

React不须要<Error boundary>把应用从事件处理器中发生的错误所引发的崩溃中恢复过来。不像render方法和生命周期方法,事件处理器的调用并无发生在渲染过程当中。可是,假如在事件处理器中抛出错误了,React也知道该如何显示它。

假设,你须要在事件处理器中捕获一个错误,你可使用常规的try/catch语句:

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

  handleClick() {
    try {
      // Do something that could throw
    } catch (error) {
      this.setState({ error });
    }
  }

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

React 15到React 16的命名更改

React 15包含了一个叫unstable_handleError的方法。这个方法对error boundaries功能的实现提供了一些有限的支持。这个方法在React 16 beta 版以后就不可用了。你能够将它替换为componentDidCatch方法。为了支持API迁移,咱们提供了一个叫codemod的类库帮助你自动升级。

相关文章
相关标签/搜索