Errorboundary详解

背景

ErrorBoundary 是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级UI,而并不会渲染那些发生崩溃的子组件树。 错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

ErrorBoundary 组件主要用于 React 应用中的错误捕获,它可以捕捉 子组件树渲染期间生命周期方法 以及 组件树内部的事件处理 中发生的错误。

ErrorBoundary 可以捕捉的错误:

  • 渲染错误 例如 undefined.someMethod() 这种运行时错误。
  • 生命周期方法中的错误 componentDidMountcomponentDidUpdatecomponentWillUnmount 等生命周期方法中发生的错误。
  • setState 触发的错误 例如:this.setState(() => { throw new Error("Oops!"); })

ErrorBoundary 示例

笔者直接提供一个现成的 ErrorBoundary 组件示例代码,具体如下:

js 复制代码
import React, { Component } from "react";

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

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

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    console.error("ErrorBoundary caught an error ------>", error, errorInfo);
  }

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

    return this.props.children;
  }
}

export default ErrorBoundary;

因为ErrorBoundary组件需要用到 getDerivedStateFromError 和 componentDidCatch 两个方法,而这两个重要方法只有类组件中有,所以 ErrorBoundary 组件只能是类组件

下面详细来看上面四种错误

渲染错误

渲染错误指React组件在渲染阶段发生的错误,该错误会被ErrorBoundary组件捕获,请看示例代码

js 复制代码
import "./App.css";

function App() {
  return (
    <>
      <RenderError />
    </>
  );
}

export default App;

// render error
function RenderError() {
  const a = null;

  a.foo(); // 运行时错误
   
  return (
    <div></div>
  );
}

上述代码出现 RenderError 组件渲染期间抛出运行时错误,会被ErrorBoundary组件捕获该错误,同时给出错误显示的自定义UI

生命周期方法中的错误

生命周期方法中的错误指在componentDidMountcomponentDidUpdatecomponentWillUnmount、useEffect等生命周期方法中发生的错误

下面是 useEffect 中抛出错误的示例代码

js 复制代码
import { useEffect } from "react";

import "./App.css";

function App() {
  return (
    <>
      <RenderError />
    </>
  );
}

export default App;

// render error
function RenderError() {
  const a = null;

  // a.foo();

  const handleClick = () => {
    a.foo();
  };

  useEffect(() => {
    a.foo();
  }, []);

  return (
    <div></div>
  );
}

在React函数组件中,用useEffect模拟mount生命周期钩子,抛出错误可以被

setState 触发的错误

setState 触发的错误也可以被ErrorBoundary 捕获,比如this.setState(() => { throw new Error("Oops!"); })

但是现在前端开发人员主要写React函数组件,setState属于React类组件语法,所以不用过于关注这种情况

前端导致的错误非常多,说完 ErrorBoundary 能够捕获的错误,下面看看 ErrorBoundary 不能捕捉的错误

ErrorBoundary 不能捕捉的错误

事件处理函数中的错误

大家一定遇到过事件处理函数中抛出的错误

js 复制代码
function RenderError() {
  const a = null;

  const handleClick = () => {
    a.foo();
  };

  return (
    <div>
      <button onClick={handleClick}>click</button>
    </div>
  );
}

ErrorBoundary 组件无法捕获这类错误,这类错误只会在console控制台输出错误,产品的界面上不会有任何变化

异步代码中的错误

js 复制代码
function RenderError() {
  const a = null;

  const handleClick = () => {
    setTimeout(() => {
      a.foo();
    });
  };

  return (
    <div>
      <button onClick={handleClick}>click</button>
    </div>
  );
}

既然事件处理函数中的错误和异步代码中的错误无法被ErrorBoundary组件捕获,但是有需要集中捕获这类错误,记录在日志中,方便定位问题和调试那怎么做呢?

因为React内部拦截事件冒泡,所以在React中的事件抛出的错误无法被捕获,那么可以对Hook一下 addEventListener 方法

js 复制代码
(function () {
  const originalAddEventListener = EventTarget.prototype.addEventListener;

  EventTarget.prototype.addEventListener = function (type, listener, options) {
    // console.log(`[Hook Event] 绑定事件: ${type}`, listener);

    const wrappedListener = function (...args) {
      try {
        return listener.apply(this, args);
      } catch (error) {
        console.error(`[Hook Event] 事件处理错误: ${type}`, error);
      }
    };

    return originalAddEventListener.call(this, type, wrappedListener, options);
  };
})();

然后在全局添加如下的代码

js 复制代码
window.addEventListener("error", (event) => {
  console.log('event', event);
});

window.addEventListener("unhandledrejection", (event) => {
  console.log('unhandledrejection error', event);
});

上述代码添加完成后,事件和异步代码中抛出的错误可以被正常捕获

服务端渲染(SSR)错误

ErrorBoundary 仅在客户端工作,无法在服务器端 React 代码(如 Next.js getServerSideProps)中捕捉错误

自身的错误

如果 ErrorBoundary 组件内部(而不是子组件)发生错误,它无法捕捉自身的错误。如果 ErrorBoundary 组件内部(而不是子组件)发生错误,它无法捕捉自身的错误。

这个问题是显而易见的

错误边界外部的错误

Reduxreducer 发生错误、全局 window 级别的 onerrorunhandledrejection 事件

最佳实践

在项目中添加ErrorBoundary.jsx,代码如下所示

js 复制代码
import React, { Component } from "react";

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

  static getDerivedStateFromError(error) {
  // 接入日志
    // Update state so the next render will show the fallback UI.
    console.log("error from getDerivedStateFromError", error);
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
  // 接入日志
    // You can also log the error to an error reporting service
    console.error("ErrorBoundary caught an error ------>", error, errorInfo);
  }

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

    return this.props.children;
  }
}

export default ErrorBoundary;

最后再添加 hook_addEventListener.js

js 复制代码
(function () {
  const originalAddEventListener = EventTarget.prototype.addEventListener;

  EventTarget.prototype.addEventListener = function (type, listener, options) {
    // console.log(`[Hook Event] 绑定事件: ${type}`, listener);

    const wrappedListener = function (...args) {
      try {
        return listener.apply(this, args);
      } catch (error) {
        console.error(`[Hook Event] 事件处理错误: ${type}`, error);
      }
    };

    return originalAddEventListener.call(this, type, wrappedListener, options);
  };
})();


window.addEventListener("error", (event) => {
   // 接入日志
  console.log('error', event);
});

window.addEventListener("unhandledrejection", (event) => {
// 接入日志
  console.log('unhandledrejection', event);
});
相关推荐
程序员与背包客_CoderZ15 分钟前
Node.js异步编程——Callback回调函数实现
前端·javascript·node.js·web
非凡ghost30 分钟前
Pale Moon:速度优化的Firefox定制浏览器
前端·firefox
清灵xmf1 小时前
从 Set、Map 到 WeakSet、WeakMap 的进阶之旅
前端·javascript·set·map·weakset·weakmap
11054654011 小时前
11、参数化三维产品设计组件 - /设计与仿真组件/parametric-3d-product-design
前端·3d
爱笑的林羽1 小时前
Mac M系列 安装 jadx-gui
前端·macos
运维@小兵2 小时前
vue使用路由技术实现登录成功后跳转到首页
前端·javascript·vue.js
肠胃炎2 小时前
React构建组件
前端·javascript·react.js
邝邝邝邝丹2 小时前
React学习———React.memo、useMemo和useCallback
javascript·学习·react.js
酷爱码2 小时前
HTML5表格语法格式详解
前端·html·html5
hello_ejb32 小时前
聊聊JetCache的缓存构建
java·前端·缓存