web前端 - React错误边界处理

369 阅读2分钟

渲染错误

React在render过程中发生错误且未被捕获,会导致js树构建失败,React会将UI从屏幕中移除,如果不处理会出现白屏。

ErrorBoundary

1.什么是ErrorBoundary

React官方文档中定义了一个名为ErrorBoundary的概念,它是一种特殊的组件,用于显示一个渲染失败的UI来代替页面崩溃(白屏)。

ErrorBoundary的颗粒度是可以控制的,可以整个页面用一个ErrorBoundary包裹,出现崩溃时直接显示一个错误页面;也可以针对部分组件,当这部分组件的渲染崩溃时渲染降级的UI部分,而不影响页面整体的渲染。

2.如何编写一个ErrorBoundary

React官方文档中的例子已经是一个可用、能复用的ErrorBoundary组件了,根据自己的业务场景稍加修改即可:

import React from 'react';
export default class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, errorMsg: '' };
  }

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

  componentDidCatch(error, info) {
    // Example "componentStack":
    //   in ComponentThatThrows (created by App)
    //   in ErrorBoundary (created by App)
    //   in div (created by App)
    //   in App
    console.log(error, info.componentStack);

  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return this.props.fallback || <div>{this.state.errorMsg}</div>;
    }

    return this.props.children;
  }
}

值得一提的是:编写一个ErrorBoundary必须使用class组件,这是class组件和function组件在能力边界上的少数区别之一。

3.如何使用ErrorBoundary

上报错误

捕获到错误后不仅可以降级渲染ui, 还可以通过componentDidCatch上报错误,作为前端页面监控的一部分。

  componentDidCatch(error, info) {
    console.log(error, info.componentStack);
    // 上报错误
    window.reporter.report(error ,info)
  }

页面级使用

全局只使用一个ErrorBoundary,当出现错误时,直接展示降级后的错误页面(FailedMain)

function App() {
  return (
      <ErrorBoundary fallback={<FailedMain/>}>
        <Main />
      </ErrorBoundary>
  )
}

这里的fallback也可以改成传入一个渲染函数,这样在ErrorBoundary内部调用时可以接收错误信息作为参数。

function App() {
  return (
      <ErrorBoundary fallback={props => <FailedMain {...props} />}>
        <Main />
      </ErrorBoundary>
  )
}

组件级使用

想象一个场景,我们有一个内容非常多的页面,页面可以分为n个子模块且互相独立,我们不希望一个子模块的崩溃导致整个页面都降级渲染,这会导致其他的正常模块也不可用。

// 这是一个用户订单页面的结构,Flight,Hotel, Ticket都是包含很多内容和功能的子模块。
// 这时候我们不希望Flight组件的崩溃导致其他的子模块不可用。
function App() {
  return (
    <>
      <ErrorBoundary>
        <Header />
      </ErrorBoundary>
      <ErrorBoundary>
        <Flight />
      </ErrorBoundary>
      <ErrorBoundary>
        <Hotel />
      </ErrorBoundary>
      <ErrorBoundary>
        <Ticket />
      </ErrorBoundary>
    </>
  )
}

上述的例子可以使用高阶组件封装,这样ui结构上会更加简单清晰。