shadcn-admin中错误边界组件设计与应用

shadcn-admin中错误边界组件设计与应用

【免费下载链接】shadcn-admin Admin Dashboard UI built with Shadcn and Vite. 【免费下载链接】shadcn-admin 项目地址: https://gitcode.com/GitHub_Trending/sh/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已经实现了多层次的错误处理,但仍存在以下局限性:

  1. 缺乏组件级错误捕获:无法捕获渲染阶段的组件错误,可能导致整个应用崩溃
  2. 错误状态分散:错误处理逻辑分散在路由、API拦截器和错误页面中
  3. 缺少错误恢复机制:仅能展示错误页面,无法在不刷新页面的情况下恢复应用状态
  4. 开发体验不足:缺少错误详情展示和快速定位功能

三、错误边界组件的设计与实现

3.1 错误边界组件的设计原则

设计一个高质量的错误边界组件应遵循以下原则:

  1. 单一职责:专注于错误捕获和处理,不包含业务逻辑
  2. 可配置性:支持自定义错误UI和错误处理行为
  3. 性能优化:避免因错误处理逻辑影响正常渲染性能
  4. 开发友好:在开发环境提供详细错误信息,生产环境展示友好提示
  5. 可扩展性:支持错误上报、日志记录等扩展功能

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中应用错误边界组件时,建议采用以下层级结构:

mermaid

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 错误边界性能优化

为避免错误边界影响应用性能,可采取以下优化措施:

  1. 错误状态缓存:避免频繁切换错误状态导致的性能问题
  2. 延迟卸载:实现错误状态的延迟卸载,防止闪烁
  3. 选择性渲染:仅在必要时渲染详细错误信息
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中实施错误边界组件带来的核心价值:

  1. 提升系统稳定性:防止单个组件错误导致整个应用崩溃
  2. 改善用户体验:提供友好的错误提示和恢复选项
  3. 优化开发效率:集中捕获错误信息,便于调试和问题定位
  4. 增强可维护性:错误处理逻辑模块化,便于扩展和维护

6.2 错误处理机制的未来演进方向

shadcn-admin错误处理机制的未来发展方向:

  1. 智能错误恢复:基于错误类型和上下文实现自动恢复
  2. 用户反馈整合:允许用户报告错误详情和复现步骤
  3. 错误预警系统:基于错误频率和严重程度实现预警
  4. AI辅助调试:结合AI技术提供错误修复建议
  5. 渐进式错误体验:根据用户角色展示不同详细程度的错误信息

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}>

【免费下载链接】shadcn-admin Admin Dashboard UI built with Shadcn and Vite. 【免费下载链接】shadcn-admin 项目地址: https://gitcode.com/GitHub_Trending/sh/shadcn-admin

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值