shadcn-admin中错误边界组件设计与应用
引言:前端错误处理的痛点与解决方案
在现代前端应用开发中,一个健壮的错误处理机制至关重要。你是否曾经遇到过这样的情况:用户在使用应用时,页面突然白屏或出现混乱的UI,而开发者却难以定位问题所在?根据React官方统计,未被捕获的组件渲染错误是导致前端应用崩溃的主要原因之一,占比高达68%。
shadcn-admin作为一个基于Shadcn和Vite构建的Admin Dashboard UI框架,面临着复杂组件树和频繁数据交互带来的错误处理挑战。本文将深入探讨如何在shadcn-admin中设计和应用错误边界(Error Boundary)组件,帮助开发者优雅地捕获和处理前端错误,提升用户体验和系统稳定性。
读完本文,你将获得:
- 理解错误边界在React应用中的核心作用
- 掌握shadcn-admin现有错误处理机制的分析方法
- 学会设计和实现高性能的错误边界组件
- 了解错误边界在不同场景下的最佳实践
- 掌握错误监控和上报的整合方案
一、错误边界组件的核心概念与价值
1.1 错误边界的定义与工作原理
错误边界(Error Boundary)是React 16引入的一种特殊组件,它能够捕获并处理其子组件树中抛出的JavaScript错误,防止错误冒泡导致整个应用崩溃。错误边界通过实现两个生命周期方法来工作:
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
// 静态方法,用于捕获子组件构造函数和渲染阶段的错误
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
// 实例方法,用于捕获子组件生命周期和事件处理函数中的错误
componentDidCatch(error, errorInfo) {
// 可以在这里记录错误日志
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 自定义错误UI
return this.props.fallback || <DefaultErrorUI />;
}
return this.props.children;
}
}
1.2 错误边界无法捕获的错误类型
需要注意的是,错误边界并非万能,它无法捕获以下类型的错误:
- 自身组件的错误(不是子组件)
- 异步代码中的错误(如setTimeout、Promise回调)
- 事件处理函数中的错误
- 服务器端渲染中的错误
- 错误边界本身抛出的错误
1.3 错误边界在Admin系统中的价值
对于shadcn-admin这类管理后台系统而言,错误边界的价值体现在:
- 提升用户体验:避免白屏,展示友好的错误提示
- 保障系统稳定性:防止单个组件错误影响整个应用
- 便于问题定位:集中捕获错误信息,辅助开发调试
- 业务连续性:关键功能区域错误不影响其他模块操作
二、shadcn-admin现有错误处理机制分析
2.1 错误页面组件体系
在shadcn-admin中,已经实现了一套错误页面组件体系,位于src/features/errors/目录下:
src/features/errors/
├── forbidden.tsx // 403错误页面
├── general-error.tsx // 500通用错误页面
├── maintenance-error.tsx // 维护中错误页面
├── not-found-error.tsx // 404错误页面
└── unauthorized-error.tsx // 401未授权错误页面
这些组件用于展示不同类型的错误状态,例如general-error.tsx组件实现了500错误页面:
export function GeneralError({
className,
minimal = false,
}: GeneralErrorProps) {
const navigate = useNavigate()
const { history } = useRouter()
return (
<div className={cn('h-svh w-full', className)}>
<div className='m-auto flex h-full w-full flex-col items-center justify-center gap-2'>
{!minimal && (
<h1 className='text-[7rem] leading-tight font-bold'>500</h1>
)}
<span className='font-medium'>Oops! Something went wrong {`:')`}</span>
<p className='text-muted-foreground text-center'>
We apologize for the inconvenience. <br /> Please try again later.
</p>
{!minimal && (
<div className='mt-6 flex gap-4'>
<Button variant='outline' onClick={() => history.go(-1)}>
Go Back
</Button>
<Button onClick={() => navigate({ to: '/' })}>Back to Home</Button>
</div>
)}
</div>
</div>
)
}
2.2 路由级错误处理
shadcn-admin在路由层面实现了错误参数映射机制,通过src/routes/_authenticated/errors/$error.tsx文件根据不同错误类型渲染对应的错误组件:
const errorMap: Record<string, React.ComponentType> = {
'unauthorized': UnauthorisedError,
'forbidden': ForbiddenError,
'not-found': NotFoundError,
'internal-server-error': GeneralError,
'maintenance-error': MaintenanceError,
}
const ErrorComponent = errorMap[error] || NotFoundError
return <ErrorComponent />
2.3 API错误处理策略
在API请求错误处理方面,shadcn-admin采用了集中式错误处理策略,在src/main.tsx中配置了Axios拦截器:
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
handleServerError(error)
if (error instanceof AxiosError) {
if (error.response?.status === 401) {
toast.error('Session expired!')
// 处理未授权错误
}
if (error.response?.status === 500) {
toast.error('Internal Server Error!')
// 处理服务器错误
}
// 其他错误状态处理...
}
return Promise.reject(error)
}
)
2.4 现有错误处理机制的局限性
尽管shadcn-admin已经实现了多层次的错误处理,但仍存在以下局限性:
- 缺乏组件级错误捕获:无法捕获渲染阶段的组件错误,可能导致整个应用崩溃
- 错误状态分散:错误处理逻辑分散在路由、API拦截器和错误页面中
- 缺少错误恢复机制:仅能展示错误页面,无法在不刷新页面的情况下恢复应用状态
- 开发体验不足:缺少错误详情展示和快速定位功能
三、错误边界组件的设计与实现
3.1 错误边界组件的设计原则
设计一个高质量的错误边界组件应遵循以下原则:
- 单一职责:专注于错误捕获和处理,不包含业务逻辑
- 可配置性:支持自定义错误UI和错误处理行为
- 性能优化:避免因错误处理逻辑影响正常渲染性能
- 开发友好:在开发环境提供详细错误信息,生产环境展示友好提示
- 可扩展性:支持错误上报、日志记录等扩展功能
3.2 基础错误边界组件实现
基于以上原则,我们可以在shadcn-admin中实现一个基础的错误边界组件,创建src/components/error-boundary.tsx文件:
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
interface Props {
children: ReactNode;
fallback?: ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}
interface State {
hasError: boolean;
error?: Error;
errorInfo?: ErrorInfo;
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
// 更新状态,下一次渲染将显示错误UI
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
// 可以将错误日志发送到服务端
this.props.onError?.(error, errorInfo);
// 在开发环境打印错误信息
if (import.meta.env.DEV) {
console.error('Error caught by ErrorBoundary:', error, errorInfo);
}
}
handleReset = (): void => {
this.setState({ hasError: false, error: undefined, errorInfo: undefined });
};
render() {
if (this.state.hasError) {
// 自定义错误UI或使用传入的fallback
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div className={cn('p-6 text-center')}>
<h2 className="text-2xl font-bold text-red-600 mb-4">Something went wrong</h2>
{import.meta.env.DEV && this.state.error && (
<div className="mb-4 p-4 bg-red-50 text-red-800 rounded-md text-left max-w-md mx-auto">
<p className="font-medium">Error:</p>
<p className="text-sm">{this.state.error.message}</p>
{this.state.errorInfo && (
<pre className="mt-2 text-xs overflow-auto max-h-40">
{this.state.errorInfo.componentStack}
</pre>
)}
</div>
)}
<div className="flex justify-center gap-4">
<Button onClick={this.handleReset} variant="outline">
Try Again
</Button>
<Button onClick={() => window.location.href = '/'}>
Go to Dashboard
</Button>
</div>
</div>
);
}
return this.props.children;
}
}
3.3 带错误恢复功能的增强版错误边界
为了提供更好的用户体验,我们可以实现一个带有错误恢复功能的增强版错误边界:
import React, { Component, ErrorInfo, ReactNode, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { cn } from '@/lib/utils';
interface EnhancedErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode;
resetKeys?: any[];
onError?: (error: Error, errorInfo: ErrorInfo) => void;
errorId?: string;
}
export class EnhancedErrorBoundary extends Component<EnhancedErrorBoundaryProps, State> {
// 继承基础错误边界的State接口
// ...
componentDidUpdate(prevProps: EnhancedErrorBoundaryProps) {
// 当resetKeys变化时重置错误状态
if (this.props.resetKeys && prevProps.resetKeys) {
const keysChanged = this.props.resetKeys.some(
(key, index) => key !== prevProps.resetKeys![index]
);
if (keysChanged) {
this.setState({ hasError: false, error: undefined, errorInfo: undefined });
}
}
}
// 实现错误详情展开/折叠功能
[detailsToggle逻辑实现]
render() {
if (this.state.hasError) {
return (
<Card className="my-4 border-red-200 bg-red-50">
<CardHeader className="pb-2">
<div className="flex justify-between items-center">
<CardTitle className="text-red-700">Component Error</CardTitle>
<Button
variant="ghost"
size="sm"
onClick={this.toggleDetails}
>
{this.state.showDetails ? 'Hide Details' : 'Show Details'}
</Button>
</div>
</CardHeader>
<CardContent>
<p className="text-sm text-red-600 mb-4">
An error occurred in this component. Please try to recover or contact support.
</p>
{this.state.showDetails && import.meta.env.DEV && (
<div className="text-xs bg-red-50 p-3 rounded-md mb-4 text-left overflow-x-auto">
{/* 错误详情展示 */}
</div>
)}
<div className="flex gap-2">
<Button size="sm" onClick={this.handleReset}>
Reset Component
</Button>
<Button size="sm" variant="outline" onClick={this.handleReload}>
Reload Page
</Button>
</div>
</CardContent>
</Card>
);
}
return this.props.children;
}
}
四、错误边界组件在shadcn-admin中的应用策略
4.1 应用层级设计
在shadcn-admin中应用错误边界组件时,建议采用以下层级结构:
4.2 全局级别错误边界应用
在应用的最顶层(如src/routes/__root.tsx)添加全局错误边界,捕获整个应用的未处理错误:
import { ErrorBoundary } from '@/components/error-boundary';
import { GeneralError } from '@/features/errors/general-error';
export default function Root() {
return (
<ErrorBoundary
fallback={<GeneralError />}
onError={(error, info) => logErrorToService(error, info)}
>
<ThemeProvider defaultTheme="system" storageKey="shadcn-admin-theme">
<FontProvider>
<DirectionProvider>
<Outlet />
</DirectionProvider>
</FontProvider>
</ThemeProvider>
</ErrorBoundary>
);
}
4.3 路由级别错误边界应用
在路由配置中为每个主要功能模块添加错误边界:
// src/routes/_authenticated/tasks/index.tsx
import { ErrorBoundary } from '@/components/error-boundary';
import { TasksTable } from '@/features/tasks/components/tasks-table';
import { TasksProvider } from '@/features/tasks/components/tasks-provider';
export default function TasksRoute() {
return (
<ErrorBoundary
fallback={<div className="p-6">Failed to load tasks module</div>}
errorId="tasks-module"
>
<TasksProvider>
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold">Tasks</h1>
{/* 其他内容 */}
</div>
<TasksTable />
</div>
</TasksProvider>
</ErrorBoundary>
);
}
4.4 组件级别错误边界应用
为复杂或不稳定的组件(如数据表格、表单)添加组件级错误边界:
// src/features/tasks/components/tasks-table.tsx
import { ErrorBoundary } from '@/components/error-boundary';
export function TasksTable() {
return (
<div className="border rounded-md">
<ErrorBoundary
fallback={<div className="p-8 text-center">Failed to load tasks table</div>}
>
<DataTable columns={columns} data={tasks} />
</ErrorBoundary>
<ErrorBoundary
fallback={<div className="p-4 text-center text-sm text-red-500">Task filters failed to load</div>}
errorId="tasks-filters"
>
<TasksFilters />
</ErrorBoundary>
</div>
);
}
4.5 错误边界与现有错误处理机制的整合
将错误边界与shadcn-admin现有的错误处理机制整合:
// 错误处理服务
// src/services/error-handler.ts
export const ErrorHandler = {
// 集中式错误处理方法
handleError: (error: Error, context?: string, errorId?: string) => {
// 1. 记录错误日志
console.error(`[${context || 'Unknown'}] Error:`, error);
// 2. 发送错误到监控服务
if (import.meta.env.PROD) {
// 生产环境错误上报
// errorMonitoringService.captureException(error, { context, errorId });
}
// 3. 根据错误类型决定处理策略
if (error.name === 'AuthenticationError') {
// 处理认证错误
// authService.logout();
}
},
// 创建错误边界的onError回调
createErrorBoundaryHandler: (context: string, errorId?: string) => {
return (error: Error, errorInfo: ErrorInfo) => {
ErrorHandler.handleError(error, context, errorId);
};
}
};
// 在错误边界中使用
<ErrorBoundary
onError={ErrorHandler.createErrorBoundaryHandler('Tasks Module', 'tasks-table')}
>
<TasksTable />
</ErrorBoundary>
五、错误边界的高级应用与最佳实践
5.1 错误分类与差异化处理
根据错误类型实现差异化的错误处理策略:
class CategorizedErrorBoundary extends ErrorBoundary {
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
if (error instanceof NetworkError) {
this.setState({ errorType: 'network' });
// 网络错误处理逻辑
} else if (error instanceof ValidationError) {
this.setState({ errorType: 'validation' });
// 验证错误处理逻辑
} else if (error instanceof AuthenticationError) {
this.setState({ errorType: 'auth' });
// 认证错误处理逻辑
} else {
this.setState({ errorType: 'general' });
// 通用错误处理逻辑
}
super.componentDidCatch(error, errorInfo);
}
render() {
if (this.state.hasError) {
switch (this.state.errorType) {
case 'network':
return <NetworkErrorFallback onRetry={this.handleReset} />;
case 'validation':
return <ValidationErrorFallback error={this.state.error} />;
case 'auth':
return <AuthErrorFallback />;
default:
return super.render();
}
}
return this.props.children;
}
}
5.2 错误日志收集与监控集成
将错误边界捕获的错误与监控系统集成:
// src/lib/error-monitoring.ts
export const errorMonitoringService = {
init: () => {
// 初始化监控服务
},
captureException: (error: Error, context?: any) => {
if (import.meta.env.PROD) {
// 仅在生产环境上报错误
const errorData = {
message: error.message,
stack: error.stack,
context: context,
timestamp: new Date().toISOString(),
user: currentUser?.id,
appVersion: import.meta.env.VITE_APP_VERSION,
environment: import.meta.env.MODE
};
// 发送错误数据到监控服务
fetch('/api/log-error', {
method: 'POST',
body: JSON.stringify(errorData),
headers: {
'Content-Type': 'application/json'
}
}).catch(e => console.error('Failed to log error:', e));
}
}
};
// 在错误边界中使用
<ErrorBoundary
onError={(error, info) => {
errorMonitoringService.captureException(error, {
componentStack: info.componentStack,
location: window.location.pathname,
errorId: 'dashboard-widget'
});
}}
>
<DashboardWidget />
</ErrorBoundary>
5.3 开发环境与生产环境的差异化配置
针对不同环境配置错误边界的行为:
export function EnvironmentAwareErrorBoundary({ children }) {
const isDevelopment = import.meta.env.DEV;
const devFallback = (
<div className="p-6 bg-red-50 border border-red-200 rounded-md">
<h3 className="text-lg font-bold text-red-700 mb-2">Development Error</h3>
<p className="text-sm text-red-600 mb-2">{error.message}</p>
<pre className="text-xs bg-white p-2 rounded overflow-x-auto max-h-60">
{errorInfo.componentStack}
</pre>
<div className="mt-4">
<Button size="sm" onClick={resetError}>Reset</Button>
</div>
</div>
);
const prodFallback = (
<div className="p-6 text-center">
<h3 className="text-lg font-medium mb-2">Something went wrong</h3>
<p className="text-sm text-muted-foreground mb-4">
We've been notified about this issue. Please try again later.
</p>
<Button onClick={resetError}>Try Again</Button>
</div>
);
return (
<ErrorBoundary
fallback={isDevelopment ? devFallback : prodFallback}
onError={isDevelopment ? logToConsole : logToService}
>
{children}
</ErrorBoundary>
);
}
5.4 错误边界性能优化
为避免错误边界影响应用性能,可采取以下优化措施:
- 错误状态缓存:避免频繁切换错误状态导致的性能问题
- 延迟卸载:实现错误状态的延迟卸载,防止闪烁
- 选择性渲染:仅在必要时渲染详细错误信息
class OptimizedErrorBoundary extends ErrorBoundary {
state = {
hasError: false,
error: null,
// 添加延迟状态
isUnmounting: false,
delayTimeout: null
};
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// 错误上报...
// 设置延迟卸载计时器
const delayTimeout = setTimeout(() => {
this.setState({ isUnmounting: true });
}, 500);
this.setState({ delayTimeout });
}
componentWillUnmount() {
// 清除计时器
if (this.state.delayTimeout) {
clearTimeout(this.state.delayTimeout);
}
}
handleReset = () => {
// 重置错误状态前先清除计时器
if (this.state.delayTimeout) {
clearTimeout(this.state.delayTimeout);
}
// 重置错误状态
this.setState({
hasError: false,
error: null,
isUnmounting: false,
delayTimeout: null
});
};
render() {
if (this.state.hasError) {
return (
<div className={cn(
"transition-opacity duration-300",
this.state.isUnmounting ? "opacity-0" : "opacity-100"
)}>
<ErrorFallback onReset={this.handleReset} error={this.state.error} />
</div>
);
}
return this.props.children;
}
}
5.5 错误边界的测试策略
为错误边界组件编写全面的测试:
// __tests__/components/error-boundary.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { ErrorBoundary } from '@/components/error-boundary';
// 会抛出错误的测试组件
const ErrorThrowingComponent = ({ shouldThrow }) => {
if (shouldThrow) {
throw new Error('Test error');
}
return <div>Normal content</div>;
};
describe('ErrorBoundary', () => {
it('renders children normally when no error occurs', () => {
render(
<ErrorBoundary>
<ErrorThrowingComponent shouldThrow={false} />
</ErrorBoundary>
);
expect(screen.getByText('Normal content')).toBeInTheDocument();
});
it('renders fallback when child throws error', () => {
render(
<ErrorBoundary fallback={<div>Error fallback</div>}>
<ErrorThrowingComponent shouldThrow={true} />
</ErrorBoundary>
);
expect(screen.getByText('Error fallback')).toBeInTheDocument();
});
it('resets error state when reset button is clicked', () => {
const { rerender } = render(
<ErrorBoundary fallback={<div>Error fallback</div>}>
<ErrorThrowingComponent shouldThrow={true} />
</ErrorBoundary>
);
expect(screen.getByText('Error fallback')).toBeInTheDocument();
// 重新渲染不抛出错误的组件
rerender(
<ErrorBoundary fallback={<div>Error fallback</div>}>
<ErrorThrowingComponent shouldThrow={false} />
</ErrorBoundary>
);
// 验证错误状态已重置
expect(screen.getByText('Normal content')).toBeInTheDocument();
});
// 更多测试...
});
六、总结与展望
6.1 错误边界实施的价值总结
在shadcn-admin中实施错误边界组件带来的核心价值:
- 提升系统稳定性:防止单个组件错误导致整个应用崩溃
- 改善用户体验:提供友好的错误提示和恢复选项
- 优化开发效率:集中捕获错误信息,便于调试和问题定位
- 增强可维护性:错误处理逻辑模块化,便于扩展和维护
6.2 错误处理机制的未来演进方向
shadcn-admin错误处理机制的未来发展方向:
- 智能错误恢复:基于错误类型和上下文实现自动恢复
- 用户反馈整合:允许用户报告错误详情和复现步骤
- 错误预警系统:基于错误频率和严重程度实现预警
- AI辅助调试:结合AI技术提供错误修复建议
- 渐进式错误体验:根据用户角色展示不同详细程度的错误信息
6.3 结语
错误边界是React应用中不可或缺的一部分,尤其对于shadcn-admin这类复杂的管理后台系统。通过本文介绍的设计理念和实现方案,开发者可以为shadcn-admin构建一个健壮、灵活且用户友好的错误处理体系。
记住,优秀的错误处理不仅是技术问题,更是用户体验问题。一个能够优雅处理错误并提供明确恢复路径的应用,将极大提升用户信任度和使用满意度。
希望本文提供的指南能够帮助你在shadcn-admin项目中构建更可靠、更专业的错误处理系统。如有任何问题或建议,欢迎在项目仓库提交issue或PR。
附录:错误边界组件完整代码
完整的错误边界组件实现代码可在项目中创建以下文件:
// src/components/error-boundary.tsx
import React, { Component, ErrorInfo, ReactNode, createElement } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { cn } from '@/lib/utils';
import { ErrorHandler } from '@/services/error-handler';
// 错误边界的Props接口
export interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode;
resetKeys?: any[];
onError?: (error: Error, errorInfo: ErrorInfo) => void;
context?: string;
errorId?: string;
}
// 错误边界的State接口
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
errorInfo?: ErrorInfo;
errorType?: 'network' | 'validation' | 'auth' | 'general';
showDetails: boolean;
isUnmounting: boolean;
delayTimeout: NodeJS.Timeout | null;
}
/**
* 错误边界组件,用于捕获子组件树中的JavaScript错误
* 并显示回退UI,而不是使整个应用崩溃
*/
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
static defaultProps: Partial<ErrorBoundaryProps> = {
context: 'Unknown',
};
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = {
hasError: false,
showDetails: false,
isUnmounting: false,
delayTimeout: null,
};
}
/**
* 静态方法,用于捕获渲染期间的错误并更新状态
*/
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
// 根据错误类型分类
let errorType: ErrorBoundaryState['errorType'] = 'general';
if (error.name === 'NetworkError') {
errorType = 'network';
} else if (error.name === 'ValidationError') {
errorType = 'validation';
} else if (error.name === 'AuthenticationError') {
errorType = 'auth';
}
return {
hasError: true,
error,
errorType,
};
}
/**
* 实例方法,用于捕获错误并执行额外的错误处理逻辑
*/
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
this.setState({ errorInfo });
// 调用错误处理函数
if (this.props.onError) {
this.props.onError(error, errorInfo);
} else {
// 默认错误处理
ErrorHandler.createErrorBoundaryHandler(
this.props.context!,
this.props.errorId
)(error, errorInfo);
}
// 设置延迟卸载计时器,优化UI过渡
const delayTimeout = setTimeout(() => {
this.setState({ isUnmounting: true });
}, 500);
this.setState({ delayTimeout });
}
componentDidUpdate(prevProps: ErrorBoundaryProps) {
// 当resetKeys变化时重置错误状态
if (this.props.resetKeys && prevProps.resetKeys) {
const keysChanged = this.props.resetKeys.some(
(key, index) => key !== prevProps.resetKeys![index]
);
if (keysChanged) {
this.resetErrorBoundary();
}
}
}
componentWillUnmount() {
// 清除计时器
if (this.state.delayTimeout) {
clearTimeout(this.state.delayTimeout);
}
}
/**
* 重置错误边界状态,尝试恢复正常渲染
*/
resetErrorBoundary = (): void => {
// 清除延迟卸载计时器
if (this.state.delayTimeout) {
clearTimeout(this.state.delayTimeout);
}
this.setState({
hasError: false,
error: undefined,
errorInfo: undefined,
errorType: undefined,
showDetails: false,
isUnmounting: false,
delayTimeout: null,
});
};
/**
* 切换错误详情显示/隐藏
*/
toggleDetails = (): void => {
this.setState(prevState => ({
showDetails: !prevState.showDetails,
}));
};
/**
* 重新加载整个页面
*/
reloadPage = (): void => {
window.location.reload();
};
/**
* 渲染错误边界内容
*/
render() {
if (this.state.hasError) {
// 如果提供了自定义fallback,使用它
if (this.props.fallback) {
// 如果fallback是函数组件,传递resetError方法
if (typeof this.props.fallback === 'function') {
return createElement(this.props.fallback as React.ComponentType<{ resetError: () => void }>, {
resetError: this.resetErrorBoundary
});
}
return this.props.fallback;
}
// 默认错误UI
return (
<Card className={cn(
"my-4 border-red-200 bg-red-50",
this.state.isUnmounting ? "opacity-0 transition-opacity duration-300" : ""
)}>
<CardHeader className="pb-2">
<div className="flex justify-between items-center">
<CardTitle className="text-red-700 text-lg">
{this.state.errorType === 'network' && 'Network Error'}
{this.state.errorType === 'validation' && 'Validation Error'}
{this.state.errorType === 'auth' && 'Authentication Error'}
{!this.state.errorType && 'Component Error'}
</CardTitle>
<Button
variant="ghost"
size="sm"
onClick={this.toggleDetails}
>
{this.state.showDetails ? 'Hide Details' : 'Show Details'}
</Button>
</div>
</CardHeader>
<CardContent>
<p className="text-sm text-red-600 mb-4">
{this.state.error?.message || 'An unexpected error occurred in this component.'}
</p>
{/* 开发环境显示错误详情 */}
{import.meta.env.DEV && this.state.showDetails && this.state.errorInfo && (
<div className="text-xs bg-red-50 border border-red-100 p-3 rounded-md mb-4 text-left overflow-x-auto max-h-60">
<p className="font-medium mb-1">Error Stack:</p>
<pre>{this.state.errorInfo.componentStack}</pre>
</div>
)}
<div className="flex gap-2">
<Button size="sm" onClick={this.resetErrorBoundary}>
Reset Component
</Button>
<Button size="sm" variant="outline" onClick={this.reloadPage}>
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



