pfan123 / Articles

经验文章
169 stars 25 forks source link

React 错误边界(Error Boundaries) #79

Open pfan123 opened 3 years ago

pfan123 commented 3 years ago

前言

React 项目中,很常见遇到页面由于某个 React 组件渲染错误(代码书写错误不规范或后端接口字段调整出错),导致整个应用被挂载出现白屏,且可能无法追踪造成影响极大,究其原因觉得是React 设计的坑点 自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。 针对此类问题,我们如何来进行感知上报以及应急兜底呢🤔️?

Error Boundaries 是 React16 提出来用来捕获渲染时错误的概念,Error Boundaries 是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,且会渲染出兜底 UI

Error Boundaries

Error Boundaries 可以用来捕获渲染时错误,API 如下:

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

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

  componentDidCatch(error, errorInfo) {
    // 错误上报
    logErrorToMyService(error, errorInfo);
  }

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

    return this.props.children; 
  }
}

这两种方法中任意一个被定义时,这个组件就会成为 Error Boundaries 组件,可以阻止子组件渲染时报错。

错误边界的工作方式类似于 JavaScriptcatch {},不同的地方在于错误边界只针对 React 组件。只有 class 组件才可以成为错误边界组件。

建议将 Error Boundary 单独作为一个组件,而不是将错误监听方法与业务组件耦合,一方面考虑到复用,另一方面则因错误检测只对子组件生效。

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

注意: 错误边界仅可以捕获其子组件的错误,无法捕获其自身的错误。如果一个错误边界无法渲染错误信息,则错误会冒泡至最近的上层错误边界,这也类似于 JavaScriptcatch {} 的工作机制。

React 16 定义和使用错误边界的例子

Error Boundaries 无法捕获错误

React Error Boundaries 官方文档 里提到了四种无法 Catch 的错误场景:

这也是使用 Error Boundaries 最容易有疑问的地方。

对于不能捕获到的错误情况, 是因为 getDerivedStateFromError 执行在 render 阶段,componentDidCatch 执行在 commit 阶段,过了这两个阶段(即一次渲染周期)就无法捕获到。

image-20201228181403967

无法捕获编译时错误

React 官方 API Error Boundaries 也只能捕获运行时错误,而对编译时错误无能为力。

编译时错误包括不限于编译环境错误、运行前的框架错误检查提示、TS/Flow 类型错误等,这些都是 Error Boundaries 无法捕获的,且没有更好的办法 Catch 住,遇到编译错误就在编译时解决吧,仅关注运行时错误就好了。

Error Boundaries 可作用于 Function Component

虽然函数式组件无法定义 Error Boundaries,但 Error Boundaries 可以捕获函数式组件的异常错误:

// ErrorBoundary 组件
class ErrorBoundary extends React.Component {
  // ...
}

// Hooks 函数组件
const Child = (props) => {
  React.useEffect(() => {
    console.log(1);
    props.a.b;
    console.log(2);
  }, [props.a.b]);

  return <div />;
};

// 可以捕获所有组件异常,包括 Function Component 的子组件
const App = () => {
  return (
    <ErrorBoundary>
      <Child />
    </ErrorBoundary>
  );
};

注意:出现在 deps 中的错误会立即被 Catch,导致 console.log(1) 都无法打印。但如果是下面的代码,则可以打印出 console.log(1),无法打印出 console.log(2):

const Child = (props) => {
  React.useEffect(() => {
    console.log(1);
    props.a.b;
    console.log(2);
  }, []);

  return <div />;
};

所以 React 官网的这句话并不是指 Error BoundariesHooks 不生效,而是指 Error Boundaries 无法以 Hooks 方式指定,对功能是没有影响的:

getSnapshotBeforeUpdate, componentDidCatch and getDerivedStateFromError: There are no Hook equivalents for these methods yet, but they will be added soon.

总结

Error Boundaries 可以捕获所有子元素渲染时异常,包括 render、各生命周期函数,但也有很多使用限制,我们需要正确使用它。

Error Boundaries 也不是万能的,更多时候我们要避免并及时修复错误以及错误兜底降低影响,并在第一时间内监控起来并快速修复

Other Resources

Error Boundaries

static-getderivedstatefromerror

componentdidcatch

use-error-boundary

React Lifecycle Methods diagram