Errorboundary详解

背景

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

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

ErrorBoundary 可以捕捉的错误:

  • 渲染错误 例如 undefined.someMethod() 这种运行时错误。
  • 生命周期方法中的错误 componentDidMount、componentDidUpdate、componentWillUnmount 等生命周期方法中发生的错误。
  • 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

生命周期方法中的错误

生命周期方法中的错误指在componentDidMount、componentDidUpdate、componentWillUnmount、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 组件内部(而不是子组件)发生错误,它无法捕捉自身的错误。

这个问题是显而易见的

错误边界外部的错误

Redux 的 reducer 发生错误、全局 window 级别的 onerror 或 unhandledrejection 事件

最佳实践

在项目中添加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);
});
相关推荐
架构技术专栏3 分钟前
Agent 工具调用:从工具声明到执行反馈
前端·人工智能·后端
别动我齐刘海42 分钟前
C++ 工程化学习
java·linux·前端·c++·学习·机器学习·机器人
志尊宝2 小时前
Vue3 零基础每日笔记(071):暗黑模式与主题切换——CSS 变量 + Element Plus 暗黑方案
前端·css·笔记·vue·html·前端开发·软件开发
刘发财2 小时前
前端如何生成10000页PDF文件?
前端·javascript·开源
A接拉起0073 小时前
2026 年 JavaScript 开发者需要了解的知识
前端·javascript·后端
用户11481867894843 小时前
面试官:大屏项目里十几个模块同时渲染上万条数据,怎么做到不卡?
前端·性能优化
前端市界3 小时前
Pro React Admin: 统一工程底座,承载多种应用...
前端·github
葡萄城技术团队3 小时前
dsh-spreadjs-editor 当前支持什么:安装前需要了解的几件事
前端
三*一3 小时前
优化修整算法
前端·javascript·算法
weifont4 小时前
自动化抓取和上架商品:能不动手,就少动手
运维·前端·自动化