【问题标题】:React error boundaries with useEffect and async function, what I'm missing?用 useEffect 和 async 函数反应错误边界,我错过了什么?
【发布时间】:2021-06-09 00:47:54
【问题描述】:

在我的Hello.jsx 组件中,我正在调用一个可能会失败的 API。这里有一个伪造的 API 被称为loader

import React, { useEffect } from "react";

export const Hello = () => {
  const loader = async () => {
    return Promise.reject("API error.");
  };

  useEffect(() => {
    const load = async () => {
      await loader();
    };

    load();
  }, []);

  return <h1>Hello World!</h1>;
};

问题是ErrorBoundary 组件(此处未显示)应该打印一条红色消息,但它没有。错误未被“捕获”。如果我通常throw,而不是在异步函数中,它会显示红色文本“出了问题!”。有什么线索吗?

<div>
  <ErrorBoundary>
    <Hello />
  </ErrorBoundary>
</div>

Complete CodeSandbox example is here.

【问题讨论】:

  • 感谢您的反馈,我已经更新了问题,希望现在更清楚。
  • @MattCarlotta 关闭叠加层,您将看到组件的正常渲染,而不是后备红色错误。

标签: reactjs


【解决方案1】:

传递给 useEffect 的函数正在成功完成。它定义了一个函数,然后成功调用该函数。效果函数正常返回undefined,所以错误边界没有什么可以捕获的。

错误边界不会捕获所有可能的错误。具体来说,from the documentation on error boundaries

错误边界不会捕获以下错误:

  • 事件处理程序(了解更多)
  • 异步代码(例如 setTimeout 或 requestAnimationFrame 回调)
  • 服务器端渲染
  • 在错误边界本身(而不是其子项)中引发的错误

如果您希望错误边界采取措施,那么一种选择是将错误保存在状态中并从组件主体中重新抛出它。例如:

function MyComponent() {
  const [error, setError] = useState(null);
  if (error) {
    throw error;
  }

  useEffect(() => {
    // If loading fails, save the error in state so the component throws when it rerenders
    load().catch(err => setError(err));
  }, []);

  return <div>...</div>
}

【讨论】:

  • 所以任何异步代码都无法捕获?那么这种情况下如何渲染ErrorBoundary呢?
  • @gremo 我已经用一个你可以尝试的选项更新了我的答案
  • 谢谢!我最终使用了来自react-error-boundaryuseErrorHandler,它们的作用完全相同。
【解决方案2】:

问题

基本上,有两件事对你不利。第一个是 CRA 有一个 ErrorOverlay,它首先捕获错误,第二个是由于事件处理程序是异步的,它们不会触发 componentDidCatch/getDerivedStateFromError 生命周期:Issue #11409

解决方法是在window 上捕获unhandledrejection 事件。

解决方案

代码

ErrorBoundary.js

import * as React from "react";

const ErrorBoundary = ({ children }) => {
  const [error, setError] = React.useState("");

  const promiseRejectionHandler = React.useCallback((event) => {
    setError(event.reason);
  }, []);

  const resetError = React.useCallback(() => {
    setError("");
  }, []);

  React.useEffect(() => {
    window.addEventListener("unhandledrejection", promiseRejectionHandler);

    return () => {
      window.removeEventListener("unhandledrejection", promiseRejectionHandler);
    };
    /* eslint-disable react-hooks/exhaustive-deps */
  }, []);

  return error ? (
    <React.Fragment>
      <h1 style={{ color: "red" }}>{error.toString()}</h1>
      <button type="button" onClick={resetError}>
        Reset
      </button>
    </React.Fragment>
  ) : (
    children
  );
};

export default ErrorBoundary;

Hello.js

import React, { useEffect } from "react";

export const Hello = () => {
  const loader = async () => {
    return Promise.reject("API Error");
  };

  useEffect(() => {
    const load = async () => {
      try {
        await loader();
      } catch (err) {
        throw err;
      }
    };

    load();
  }, []);

  return <h1>Hello World!</h1>;
};

index.js

import React from "react";
import { render } from "react-dom";
import { Hello } from "./Hello";
import ErrorBoundary from "./ErrorBoundary";

const App = () => (
  <ErrorBoundary>
    <Hello />
  </ErrorBoundary>
);

render(<App />, document.getElementById("root"));

其他想法

更简洁的方法是仅显示有关错误的弹出窗口/通知,而不是覆盖整个 UI。更大、更复杂的 UI 意味着不必要的大 UI 重绘:

【讨论】:

  • 谢谢!我最终使用了react-error-boundary 中的useErrorHandler,它们的作用完全相同。
【解决方案3】:

如果类组件定义了生命周期方法静态 getDerivedStateFromError() 或 componentDidCatch() 中的一个(或两个),它就会成为错误边界。抛出错误后,使用静态 getDerivedStateFromError() 呈现回退 UI。使用 componentDidCatch() 记录错误信息。

参考: https://reactjs.org/docs/error-boundaries.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-21
    • 2020-11-25
    • 1970-01-01
    • 2019-08-29
    • 2015-10-06
    • 1970-01-01
    • 2020-03-30
    相关资源
    最近更新 更多