React:在应用程序中处理错误(Error Boundary)

425 阅读5分钟

React:在应用程序中处理错误(Error Boundary)

引言

在这篇文章中,我们将探讨如何在React中自带的处理错误。React提供了一种错误边界机制,称为Error Boundary,可以用来捕获应用程序中发生的错误。接下来,我们将详细介绍如何使用Error Boundary进行错误处理。

环境

  • 操作系统:Windows 10
  • Vite版本:v2.7.2
  • Node版本:v19.0.0
  • React版本:v19.0.0
  • TypeScript版本:v5

创建一个没有错误的示例程序

首先,我们将创建一个没有错误的示例程序。

以下是具体代码:

main.tsx

import App from "./App";

export default function Home() {
  return <App />;
}

components/App.tsx

import Page1 from "@/components/Page1";
import Page2 from "@/components/Page2";
import Page3 from "@/components/Page3";

export default function App() {
  return (
    <>
      <Page1 />
      <Page2 />
      <Page3 />
    </>
  );
}

components/Page1.tsx

export default function Page1() {
  return (
    <div style={{ backgroundColor: "lightblue", paddingBottom: "20px" }}>
      <h3>Page1</h3>
    </div>
  );
}

components/Page2.tsx

export default function Page2() {
  return (
    <div style={{ backgroundColor: "yellow", paddingBottom: "20px" }}>
      <h3>Page2</h3>
    </div>
  );
}

components/Page3.tsx

import Page3Child from "./Page3Child";

export default function Page3() {
  return (
    <div style={{ backgroundColor: "grey", paddingBottom: "20px" }}>
      <h3>Page3</h3>
      <Page3Child />
    </div>
  );
}

components/Page3Child.tsx

export default function Page3Child() {
  return (
    <div style={{ backgroundColor: "orange", paddingBottom: "20px" }}>
      <h5>Page 3 Child</h5>
    </div>
  );
}

在组件中发生错误

接下来,为了演示Error Boundary是如果捕获并处理异常的。我们将修改Page3Child组件的代码,使其发生错误。

components/Page3Child.tsx

function Page3Child() {
  const title = 'Page3Child';

  return (
    <div style={{ backgroundColor: '#DEB331' }}>
      {/* hoge未声明,因此会引发错误 */}
      <h5>{hoge}</h5>
    </div>
  );
}
export default Page3Child;

当组件中发生错误时,整个页面会跳转错误信息页面,控制台中会输出错误信息。

安装ErrorBoundary

为了处理错误,我们使用react自带的ErrorBoundary。不需要安装包命令进行安装。

创建ErrorBoundary组件

接下来,我们将创建一个ErrorBoundary组件,用于处理子组件树中发生的错误。ErrorBoundary组件将使用类组件来创建,并定义static getDerivedStateFromError()componentDidCatch()方法。static getDerivedStateFromError()用于在错误被抛出后渲染备用UI,而componentDidCatch()用于记录错误信息(可选)。

根据官方文档,定义了生命周期方法static getDerivedStateFromError()componentDidCatch()的类组件将成为Error Boundary。如果省略static getDerivedStateFromError()方法,则不会显示备用UI(错误信息组件),ErrorBoundary组件本身将会出错。

错误信息如下:

ErrorBoundary: Error boundaries should implement getDerivedStateFromError(). In that method, return a state update to display an error message or fallback UI. 警告中文:ErrorBoundary:错误边界应该实现getDerivedStateFromError()。在该方法中,返回一个状态更新以显示错误消息或备用UI。

components/ErrorBoundary.tsx

import React, { Component, ErrorInfo } from "react";

type Props = {
  children: React.ReactNode;
};

type State = {
  hasError: boolean;
};

class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error("Uncaught error:", error, errorInfo);
  }

  render(): React.ReactNode {
    return this.state.hasError ? <h1>发生错误</h1> : this.props.children;
  }
}

export default ErrorBoundary;

我们将使用创建的ErrorBoundary组件来包裹Page3组件中的Page3Child组件。

components/Page3.tsx

"use client";

import ErrorBoundary from "./ErrorBoundary";
import Page3Child from "./Page3Child";

export default function Page3() {
  return (
    <div style={{ backgroundColor: "grey", paddingBottom: "20px" }}>
      <h3>Page3</h3>
      <ErrorBoundary>
        <Page3Child />
      </ErrorBoundary>
    </div>
  );
}

Page3Child组件将引发错误。

components/Page3Child.tsx

export default function Page3Child() {
  const title = "Page3Child";
  return (
    <div style={{ backgroundColor: "orange", paddingBottom: "20px" }}>
      {/* hoge未声明,因此会引发错误 */}
      <h5>{hoge}</h5>
    </div>
  );
}

当用ErrorBoundary组件包裹后,Page3Child组件部分将显示ErrorBoundary定义的异常信息组件。

修改ErrorBoundary组件的包裹范围

我们将尝试修改ErrorBoundary组件的包裹范围。首先,我们将删除在Page3组件中使用的ErrorBoundary组件,并在App组件中包裹Page3组件。

components/App.tsx

"use client";

import Page1 from "@/components/Page1";
import Page2 from "@/components/Page2";
import Page3 from "@/components/Page3";
import ErrorBoundary from "@/components/ErrorBoundary";

export default function App() {
  return (
    <>
      <Page1 />
      <Page2 />
      <ErrorBoundary>
        <Page3 />
      </ErrorBoundary>
    </>
  );
}

此时,Page3部分将显示ErrorBoundary定义的异常信息组件。

接下来,我们将Page2和Page3组件包裹在ErrorBoundary组件中。

components/App.tsx

"use client";

import Page1 from "@/components/Page1";
import Page2 from "@/components/Page2";
import Page3 from "@/components/Page3";
import ErrorBoundary from "@/components/ErrorBoundary";

export default function App() {
  return (
    <>
      <Page1 />
      <ErrorBoundary>
        <Page2 />
        <Page3 />
      </ErrorBoundary>
    </>
  );
}

Page2和Page3的内容没有显示,而替换成了ErrorBoundary定义的异常信息组件

最后,我们将所有组件都包裹在ErrorBoundary组件中。

components/App.tsx

export default function App() {
  return (
    <>
      <ErrorBoundary>
        <Page1 />
        <Page2 />
        <Page3 />
      </ErrorBoundary>
    </>
  );
}

此时,整个页面内容没有显示,而完全替换成了ErrorBoundary定义的异常信息组件。

Error Boundary无法捕获的错误

接下来我们介绍一下Error Boundary的局限性,React自带的Error Boundary无法捕获以下类型的错误:

  • 在事件处理程序中发生的错误
  • 在异步处理中的错误
  • 服务器端渲染中发生的错误
  • Error Boundary自身抛出的错误

在事件处理程序中引发错误

我们将尝试在事件处理程序中引发错误。我们将创建一个新的Page4组件。

components/Page4.tsx

export function Page4() {
  const onClick = () => {
    throw new Error("Page4 error");
  };

  return (
    <div style={{ padding: "20px", backgroundColor: "lightgray" }}>
      <h3>Page4</h3>
      <button type="button" onClick={onClick}>
        按钮
      </button>
    </div>
  );
}

当点击Page4的按钮时,控制台中会显示错误,但页面不会发生任何变化。

在异步处理过程中引发错误

接下来,我们将尝试在异步处理过程中引发错误。我们将修改Page4组件以避免引发错误,并创建一个新的Page5组件。

components/Page5.tsx

import { useEffect, useState } from "react";

type UserType = {
  name: string;
  age: number;
};

const fetchUserApi = async (): Promise<UserType> => {
  const user = { name: "John Doe", age: 30 };
  throw new Error("fetchUserAPI报错");
  return user;
};

function Page5() {
  const [user, setUser] = useState<UserType>();
  useEffect(() => {
    const fetchData = async () => {
      setUser(await fetchUserApi());
    };
    fetchData();
  }, []);

  return (
    <div style={{ backgroundColor: "pink", padding: "20px" }}>
      <div>
        <h3>Page5</h3>
        <p>用户名:{user?.name}</p>
      </div>
    </div>
  );
}

export default Page5;


同样,控制台中会显示错误,但页面不会发生任何变化。

总结

使用Error Boundary进行错误处理非常简单。然而,对于在事件处理程序中引发的错误和异步处理中的错误,Error Boundary并没有提供有效的解决方案。下一步,我将研究如何使用“react-error-boundary”来处理组件中引发的错误、事件处理程序中的错误以及异步处理中的错误。


希望这篇文章能帮助你更好地理解React中的错误处理机制!