使用 useEffect 和异步函数反应错误边界,我缺少什么?

gre*_*emo 15 reactjs

在我的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>;
};
Run Code Online (Sandbox Code Playgroud)

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

<div>
  <ErrorBoundary>
    <Hello />
  </ErrorBoundary>
</div>
Run Code Online (Sandbox Code Playgroud)

完整的 CodeSandbox 示例在这里

Ste*_*ngs 17

传递给 useEffect 的函数已成功完成。它定义了一个函数,然后成功调用该函数。效果函数undefined正常返回,因此错误边界没有捕获任何内容。

错误边界并不能捕获所有可能的错误。具体来说,来自错误边界的文档

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

  • 事件处理程序(了解更多)
  • 异步代码(例如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>
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢!我最终使用了“react-error-boundary”中的“useErrorHandler”,它的作用完全相同。 (3认同)

Mat*_*tta 5

问题

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

解决方法是unhandledrejection捕获window.

解决方案

编辑 React 错误边界 Promise

代码

错误边界.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;
Run Code Online (Sandbox Code Playgroud)

你好.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>;
};
Run Code Online (Sandbox Code Playgroud)

索引.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"));
Run Code Online (Sandbox Code Playgroud)

其他想法

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

编辑 React 错误 UI 通知