Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 错误边界(Error Boundaries) #79

Open
pfan123 opened this issue Dec 28, 2020 · 0 comments
Open

React 错误边界(Error Boundaries) #79

pfan123 opened this issue Dec 28, 2020 · 0 comments

Comments

@pfan123
Copy link
Owner

pfan123 commented Dec 28, 2020

前言

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; 
  }
}
  • static getDerivedStateFromError:在出错后有机会修改 state 触发最后一次错误 fallback 的渲染。
  • componentDidCatch:用于出错时副作用代码,比如错误上报等。

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

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

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

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

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

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

Error Boundaries 无法捕获错误

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

  • 回调事件,由于回调事件执行时机不在渲染周期内,因此无法被 Error Boundaries Catch 住,如有必要得自行 try/catch

  • 异步代码,比如 setTimeoutrequestAnimationFrame,和第一条同理

  • 服务端渲染

  • Error Boundaries 组件自身触发的错误,只能捕获其子组件的错误

这也是使用 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant