ErrorBoundary 使用详解
ErrorBoundary(错误边界)是 React 提供的一种组件错误处理机制,用于捕获并处理子组件树中 JavaScript 错误,防止整个应用崩溃。
一、基本概念
1. 什么是 ErrorBoundary?
- 一个 React 组件,可以捕获其子组件树中发生的 JavaScript 错误
- 记录这些错误,并显示备用 UI 而不是崩溃的组件树
- 类似于 JavaScript 的
try-catch,但是针对组件
2. 能捕获哪些错误?
- 子组件的渲染错误
- 生命周期方法中的错误
- 构造函数中的错误
3. 不能捕获哪些错误?
- 事件处理程序中的错误(使用常规 try-catch)
- 异步代码中的错误(setTimeout、fetch 等)
- 服务端渲染错误
- 错误边界组件自身抛出的错误
二、基本使用
1. 类组件实现 ErrorBoundary
jsx
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示备用 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你可以将错误日志上报到服务器
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定义备用 UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
2. 使用方式
jsx
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
三、高级用法
1. 带错误重置功能的 ErrorBoundary
jsx
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
this.resetError = this.resetError.bind(this);
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
this.setState({ error, errorInfo });
logErrorToMyService(error, errorInfo);
}
resetError() {
this.setState({ hasError: false, error: null, errorInfo: null });
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong.</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
<button onClick={this.resetError}>Try again</button>
</div>
);
}
return this.props.children;
}
}
2. 使用 Hooks 的函数组件实现
虽然官方推荐使用类组件实现 ErrorBoundary,但可以通过包装类组件来在函数组件中使用:
jsx
function useErrorBoundary() {
const [error, setError] = useState(null);
const ErrorBoundary = useMemo(() => {
return class extends React.Component {
static getDerivedStateFromError(error) {
setError(error);
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error(error, errorInfo);
}
render() {
return this.props.children;
}
};
}, []);
return { ErrorBoundary, error };
}
// 使用
function App() {
const { ErrorBoundary, error } = useErrorBoundary();
if (error) {
return <div>Error: {error.message}</div>;
}
return (
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
);
}
3. 使用 react-error-boundary 库
第三方库 react-error-boundary 提供了更强大的功能:
bash
npm install react-error-boundary
使用示例:
jsx
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// 重置应用状态
}}
onError={(error, info) => {
// 记录错误日志
}}
>
<MyApp />
</ErrorBoundary>
);
}
四、最佳实践
1. 错误边界的位置
- 在路由级别使用:防止整个页面崩溃
- 在复杂组件周围使用:防止部分 UI 崩溃
- 避免在最顶层使用:这样会隐藏所有错误
jsx
function App() {
return (
<ErrorBoundary FallbackComponent={PageErrorFallback}>
<Router>
<Routes>
<Route path="/" element={
<ErrorBoundary FallbackComponent={DashboardErrorFallback}>
<Dashboard />
</ErrorBoundary>
} />
<Route path="/profile" element={
<ErrorBoundary FallbackComponent={ProfileErrorFallback}>
<Profile />
</ErrorBoundary>
} />
</Routes>
</Router>
</ErrorBoundary>
);
}
2. 错误恢复
提供重置错误的机制,让用户可以恢复应用状态:
jsx
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div>
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => window.location.reload()}
>
<MyApp />
</ErrorBoundary>
);
}
3. 错误日志记录
将错误信息发送到错误监控服务:
jsx
class ErrorBoundary extends React.Component {
// ...
componentDidCatch(error, errorInfo) {
// 使用错误监控服务
Sentry.captureException(error, { extra: errorInfo });
// 或自定义日志
logErrorToMyService(error, errorInfo.componentStack);
}
// ...
}
4. 与 Suspense 结合使用
处理异步加载错误:
jsx
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Suspense fallback={<Loading />}>
<LazyComponent />
</Suspense>
</ErrorBoundary>
五、常见问题解决方案
1. 事件处理程序中的错误
ErrorBoundary 不能捕获事件处理程序中的错误,需要单独处理:
jsx
function MyComponent() {
const handleClick = () => {
try {
// 可能出错的代码
} catch (error) {
// 处理错误
logError(error);
}
};
return <button onClick={handleClick}>Click me</button>;
}
2. 异步错误处理
ErrorBoundary 不能捕获异步代码错误,需要转换为同步错误:
jsx
function AsyncWrapper({ children }) {
const [error, setError] = useState(null);
if (error) {
throw error; // 让 ErrorBoundary 捕获
}
return React.cloneElement(children, {
// 将异步错误转换为同步错误
onError: (error) => setError(error)
});
}
// 使用
<ErrorBoundary>
<AsyncWrapper>
<MyAsyncComponent />
</AsyncWrapper>
</ErrorBoundary>
3. 多个错误边界嵌套
可以嵌套多个 ErrorBoundary 以处理不同级别的错误:
jsx
<ErrorBoundary FallbackComponent={AppError}>
<Header />
<ErrorBoundary FallbackComponent={SidebarError}>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary FallbackComponent={MainContentError}>
<MainContent />
</ErrorBoundary>
<Footer />
</ErrorBoundary>
六、总结
- ErrorBoundary 是 React 的错误处理机制,用于捕获子组件树的 JavaScript 错误
- 实现方式:通过类组件的
getDerivedStateFromError和componentDidCatch生命周期方法 - 最佳实践:
- 在适当层级使用(路由级别、复杂组件周围)
- 提供错误恢复机制
- 记录错误日志
- 限制:不能捕获事件处理、异步代码、服务端渲染和自身错误
- 增强方案:可以使用
react-error-boundary等第三方库
通过合理使用 ErrorBoundary,可以显著提高 React 应用的健壮性和用户体验。
