React 16 中的异常处理

React 16 中的异常处理翻译自 React 官方文档,从属于笔者的 React 与前端工程化实践系列中的 React 组件分割与解耦章节;也可使用 create-webpack-app 运行本部分示例 。前端

异常处理

在 React 15.x 及以前的版本中,组件内的异常有可能会影响到 React 的内部状态,进而致使下一轮渲染时出现未知错误。这些组件内的异常每每也是由应用代码自己抛出,在以前版本的 React 更多的是交托给了开发者处理,而没有提供较好地组件内优雅处理这些异常的方式。在 React 16.x 版本中,引入了所谓 Error Boundary 的概念,从而保证了发生在 UI 层的错误不会连锁致使整个应用程序崩溃;未被任何异常边界捕获的异常可能会致使整个 React 组件树被卸载。所谓的异常边界即指某个可以捕获它的子元素(包括嵌套子元素等)抛出的异常,而且根据用户配置进行优雅降级地显示而不是致使整个组件树崩溃。异常边界可以捕获渲染函数、生命周期回调以及整个组件树的构造函数中抛出的异常。
咱们能够经过为某个组件添加新的 componentDidCatch(error, info) 生命周期回调来使其变为异常边界:webpack

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

  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // 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;
  }
}

而后咱们就能够如常使用该组件:web

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

componentDidCatch() 方法就好像针对组件的 catch {} 代码块;不过 JavaScript 中的 try/catch 模式更多的是面向命令式代码,而 React 组件自己是声明式模式,所以更适合采用指定渲染对象的模式。须要注意的是仅有类组件能够成为异常边界,在真实的应与开发中咱们每每会声明单个异常边界而后在全部可能抛出异常的组件中使用它。另外值得一提的是异常边界并不能捕获其自己的异常,若是异常边界组件自己抛出了异常,那么会冒泡传递到上一层最近的异常边界中。
在真实地应用开发中有的开发者也会将崩坏的界面直接展现给开发者,不过譬如在某个聊天界面中,若是在出现异常的状况下仍然直接将界面展现给用户,就有可能致使用户将信息发送给错误的接受者;或者在某些支付应用中致使用户金额显示错误。所以若是咱们将应用升级到 React 16.x,咱们须要将本来应用中没有被处理地异常统一包裹进异常边界中。譬如某个应用中可能会分为侧边栏、信息面板、会话界面、信息输入等几个不一样的模块,咱们能够将这些模块包裹进不一样的错误边界中;这样若是某个组件发生崩溃,会被其直属的异常边界捕获,从而保证剩余的部分依然处于可用状态。一样的咱们也能够在异常边界中添加错误反馈等服务接口以及时反馈生产环境下的异常而且修复他们。完整的应用代码以下所示:前端工程化

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }
  
  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
    // You can also log error messages to an error reporting service here
  }
  
  render() {
    if (this.state.errorInfo) {
      // Error path
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }
    // Normally, just render children
    return this.props.children;
  }  
}

class BuggyCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    this.setState(({counter}) => ({
      counter: counter + 1
    }));
  }
  
  render() {
    if (this.state.counter === 5) {
      // Simulate a JS error
      throw new Error('I crashed!');
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
  }
}

function App() {
  return (
    <div>
      <p>
        <b>
          This is an example of error boundaries in React 16.
          <br /><br />
          Click on the numbers to increase the counters.
          <br />
          The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.
        </b>
      </p>
      <hr />
      <ErrorBoundary>
        <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p>
        <BuggyCounter />
        <BuggyCounter />
      </ErrorBoundary>
      <hr />
      <p>These two counters are each inside of their own error boundary. So if one crashes, the other is not affected.</p>
      <ErrorBoundary><BuggyCounter /></ErrorBoundary>
      <ErrorBoundary><BuggyCounter /></ErrorBoundary>
    </div>
  );
}



ReactDOM.render(
  <App />,
  document.getElementById('root')
);
相关文章
相关标签/搜索