出发点
React ErrorBoundary 作用: 防止页面崩溃
在使用错误边界组件的时候,我发现路由失效了,在此分享我的解决方案
问题复现
ErrorBoundary 组件使用 componentDidCatch 生命周期捕获错误,因此只能使用 class 组件
import React from 'react'
import { BrowserRouter, Link, Routes, Route } from "react-router-dom";
import Homepage from './Homepage';
import PageWithError from './PageWithError';
export default function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>{" "}
<Link to="/page-with-error">Broken Page</Link>
</nav>
<ErrorBoundary>
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="/page-with-error" element={<PageWithError />} />
</Routes>
</ErrorBoundary>
</BrowserRouter>
);
}
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
return this.state.hasError
? <h1>Something went wrong.</h1>
: this.props.children;
}
}
如此使用后,再次进行路由跳转,发现路由全部失效了。
解决方案
在网上兜兜转转,终于找到了原因: 在捕获错误后,组件的 hasError 依旧为 true,导致路由组件没有被渲染 解决方法是,在外面再包裹一个组件,用以在 ErrorBoundary 组件重渲染的时候渲染路由组件。
import type { Dispatch, ErrorInfo, FC, ReactNode, SetStateAction } from 'react';
import { Component, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
interface PropsType {
children: ReactNode;
hasError: boolean;
ErrorComponent?: FC<StateType>;
setHasError: Dispatch<SetStateAction<boolean>>;
}
interface StateType {
hasError: boolean;
error: null | Error;
errorInfo: null | ErrorInfo;
}
class ErrorBoundaryInner extends Component<PropsType, StateType> {
constructor(props: PropsType) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
};
}
componentDidUpdate(prevProps: PropsType) {
if (!this.props.hasError && prevProps.hasError) {
this.setState({ hasError: false });
}
}
// 捕获抛出异常
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
this.props.setHasError(true);
// 传递异常信息
this.setState({
hasError: true,
error,
errorInfo,
});
}
render() {
// 如果捕获到异常,渲染降级UI
if (this.state.hasError) {
return (
this.props.ErrorComponent?.(this.state) || (
<div>
<h1>{`Error:${this.state.error?.message}`}</h1>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.errorInfo?.componentStack}
</details>
</div>
)
);
}
return this.props.children;
}
}
const ErrorBoundary: FC<any> = ({ children }) => {
const [hasError, setHasError] = useState(false);
const location = useLocation();
useEffect(() => {
if (hasError) {
setHasError(false);
}
}, [location.key]);
return (
<ErrorBoundaryInner hasError={hasError} setHasError={setHasError}>
{children}
</ErrorBoundaryInner>
);
};
export default ErrorBoundary;
参考文章:Error boundary causes React Router links to stop working