问题
基本上,有两件事对你不利。第一个是 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 重绘: