Skip to content

错误边界深入 - React错误处理完全指南

1. 错误边界基础

1.1 什么是错误边界

typescript
const errorBoundaryDefinition = {
  定义: 'React组件,可以捕获其子组件树中的JavaScript错误,记录错误,并显示备用UI',
  作用: [
    '防止整个应用崩溃',
    '提供优雅的错误处理',
    '记录错误日志',
    '提升用户体验'
  ],
  限制: [
    '不能捕获事件处理器中的错误',
    '不能捕获异步代码中的错误(setTimeout, Promise等)',
    '不能捕获服务端渲染的错误',
    '不能捕获错误边界自身的错误'
  ],
  使用场景: [
    '保护关键UI区域',
    '第三方组件包裹',
    '路由级别错误处理',
    '懒加载组件保护'
  ]
};

1.2 基础实现

jsx
// 类组件实现错误边界
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null, errorInfo: null };
  }
  
  // 渲染备用UI
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  // 记录错误信息
  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
    
    this.setState({
      error,
      errorInfo
    });
    
    // 发送错误到日志服务
    logErrorToService(error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h2>Something went wrong.</h2>
          {process.env.NODE_ENV === 'development' && (
            <details style={{ whiteSpace: 'pre-wrap' }}>
              {this.state.error && this.state.error.toString()}
              <br />
              {this.state.errorInfo && this.state.errorInfo.componentStack}
            </details>
          )}
        </div>
      );
    }
    
    return this.props.children;
  }
}

// 使用
function App() {
  return (
    <ErrorBoundary>
      <MyComponent />
    </ErrorBoundary>
  );
}

1.3 函数组件错误边界(实验性)

jsx
// 使用react-error-boundary库
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}
      onError={(error, errorInfo) => {
        console.error('Error:', error, errorInfo);
      }}
      onReset={() => {
        // 重置应用状态
      }}
    >
      <MyComponent />
    </ErrorBoundary>
  );
}

// 使用render prop
function AppWithRenderProp() {
  return (
    <ErrorBoundary
      fallbackRender={({ error, resetErrorBoundary }) => (
        <div>
          <p>Error: {error.message}</p>
          <button onClick={resetErrorBoundary}>Reset</button>
        </div>
      )}
    >
      <MyComponent />
    </ErrorBoundary>
  );
}

2. 高级错误边界实现

2.1 可配置的错误边界

jsx
class ConfigurableErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null,
      errorCount: 0
    };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    const { onError, maxRetries = 3 } = this.props;
    
    this.setState(prevState => ({
      error,
      errorInfo,
      errorCount: prevState.errorCount + 1
    }));
    
    // 调用onError回调
    if (onError) {
      onError(error, errorInfo);
    }
    
    // 自动重试
    if (this.state.errorCount < maxRetries && this.props.autoRetry) {
      setTimeout(() => {
        this.reset();
      }, this.props.retryDelay || 1000);
    }
  }
  
  reset = () => {
    this.setState({
      hasError: false,
      error: null,
      errorInfo: null
    });
  };
  
  render() {
    const { hasError, error, errorInfo, errorCount } = this.state;
    const {
      fallback,
      fallbackComponent: FallbackComponent,
      children,
      showDetails = process.env.NODE_ENV === 'development'
    } = this.props;
    
    if (hasError) {
      // 自定义Fallback组件
      if (FallbackComponent) {
        return (
          <FallbackComponent
            error={error}
            errorInfo={errorInfo}
            errorCount={errorCount}
            reset={this.reset}
          />
        );
      }
      
      // Fallback元素
      if (fallback) {
        return fallback;
      }
      
      // 默认Fallback UI
      return (
        <div style={{ padding: '20px', border: '1px solid red' }}>
          <h2>Error Occurred</h2>
          <p>Attempt {errorCount}</p>
          <button onClick={this.reset}>Retry</button>
          {showDetails && (
            <details style={{ whiteSpace: 'pre-wrap', marginTop: '10px' }}>
              <summary>Error Details</summary>
              <p>{error && error.toString()}</p>
              <p>{errorInfo && errorInfo.componentStack}</p>
            </details>
          )}
        </div>
      );
    }
    
    return children;
  }
}

// 使用
function App() {
  const handleError = (error, errorInfo) => {
    // 发送到错误追踪服务
    Sentry.captureException(error, { extra: errorInfo });
  };
  
  return (
    <ConfigurableErrorBoundary
      onError={handleError}
      maxRetries={3}
      autoRetry={true}
      retryDelay={2000}
      showDetails={true}
    >
      <MyComponent />
    </ConfigurableErrorBoundary>
  );
}

2.2 多级错误边界

jsx
// 顶层错误边界: 捕获整个应用的错误
class RootErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    console.error('[Root] Error:', error, errorInfo);
    logErrorToService(error, { level: 'critical', ...errorInfo });
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div style={{ textAlign: 'center', padding: '50px' }}>
          <h1>Application Error</h1>
          <p>We're sorry, something went wrong.</p>
          <button onClick={() => window.location.reload()}>
            Reload Page
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}

// 功能区域错误边界: 保护特定功能
class FeatureErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    console.error(`[Feature: ${this.props.featureName}] Error:`, error);
    logErrorToService(error, {
      level: 'warning',
      feature: this.props.featureName,
      ...errorInfo
    });
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div style={{ padding: '20px', background: '#ffe6e6' }}>
          <h3>{this.props.featureName} Error</h3>
          <p>This feature is temporarily unavailable.</p>
        </div>
      );
    }
    return this.props.children;
  }
}

// 组件级错误边界: 保护单个组件
class ComponentErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    console.error(`[Component: ${this.props.componentName}] Error:`, error);
  }
  
  render() {
    if (this.state.hasError) {
      return this.props.fallback || null;
    }
    return this.props.children;
  }
}

// 应用结构
function App() {
  return (
    <RootErrorBoundary>
      <Header />
      
      <main>
        <FeatureErrorBoundary featureName="User Dashboard">
          <Dashboard />
        </FeatureErrorBoundary>
        
        <FeatureErrorBoundary featureName="Notifications">
          <ComponentErrorBoundary
            componentName="NotificationPanel"
            fallback={<div>Notifications unavailable</div>}
          >
            <NotificationPanel />
          </ComponentErrorBoundary>
        </FeatureErrorBoundary>
        
        <FeatureErrorBoundary featureName="Chat">
          <ChatWidget />
        </FeatureErrorBoundary>
      </main>
      
      <Footer />
    </RootErrorBoundary>
  );
}

2.3 错误边界工厂

jsx
// 创建统一的错误边界工厂
function createErrorBoundary(config = {}) {
  const {
    name = 'ErrorBoundary',
    onError = () => {},
    fallback = null,
    level = 'component'  // 'root', 'feature', 'component'
  } = config;
  
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = { hasError: false, error: null, errorInfo: null };
    }
    
    static getDerivedStateFromError(error) {
      return { hasError: true, error };
    }
    
    componentDidCatch(error, errorInfo) {
      this.setState({ errorInfo });
      onError(error, errorInfo, { name, level });
    }
    
    reset = () => {
      this.setState({ hasError: false, error: null, errorInfo: null });
    };
    
    render() {
      if (this.state.hasError) {
        if (typeof fallback === 'function') {
          return fallback({
            error: this.state.error,
            errorInfo: this.state.errorInfo,
            reset: this.reset
          });
        }
        return fallback;
      }
      return this.props.children;
    }
  };
}

// 使用工厂创建不同级别的错误边界
const RootErrorBoundary = createErrorBoundary({
  name: 'Root',
  level: 'root',
  onError: (error, errorInfo, context) => {
    Sentry.captureException(error, {
      level: 'fatal',
      contexts: { react: errorInfo, custom: context }
    });
  },
  fallback: ({ reset }) => (
    <div>
      <h1>Fatal Error</h1>
      <button onClick={() => window.location.reload()}>Reload</button>
    </div>
  )
});

const FeatureErrorBoundary = createErrorBoundary({
  name: 'Feature',
  level: 'feature',
  onError: (error, errorInfo, context) => {
    Sentry.captureException(error, {
      level: 'error',
      contexts: { react: errorInfo, custom: context }
    });
  },
  fallback: ({ error, reset }) => (
    <div>
      <p>Feature Error: {error.message}</p>
      <button onClick={reset}>Try Again</button>
    </div>
  )
});

3. 错误边界最佳实践

3.1 路由级别错误边界

jsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

// 路由错误边界包装器
function RouteErrorBoundary({ children }) {
  return (
    <ErrorBoundary
      fallbackRender={({ error, resetErrorBoundary }) => (
        <div>
          <h2>Page Error</h2>
          <p>{error.message}</p>
          <button onClick={resetErrorBoundary}>Go Back</button>
        </div>
      )}
      onError={(error, errorInfo) => {
        console.error('Route Error:', error);
        logErrorToService(error, { route: window.location.pathname, ...errorInfo });
      }}
    >
      <Suspense fallback={<div>Loading...</div>}>
        {children}
      </Suspense>
    </ErrorBoundary>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route
          path="/"
          element={
            <RouteErrorBoundary>
              <Home />
            </RouteErrorBoundary>
          }
        />
        <Route
          path="/about"
          element={
            <RouteErrorBoundary>
              <About />
            </RouteErrorBoundary>
          }
        />
        <Route
          path="/dashboard"
          element={
            <RouteErrorBoundary>
              <Dashboard />
            </RouteErrorBoundary>
          }
        />
      </Routes>
    </BrowserRouter>
  );
}

3.2 异步错误处理

jsx
// 错误边界不能捕获异步错误,需要手动处理
function AsyncComponent() {
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetchData()
      .then(setData)
      .catch(setError)  // 捕获异步错误
      .finally(() => setLoading(false));
  }, []);
  
  // 手动抛出错误,让错误边界捕获
  if (error) {
    throw error;
  }
  
  if (loading) return <div>Loading...</div>;
  return <div>{data}</div>;
}

// 更优雅的方式: 自定义Hook
function useAsyncError() {
  const [, setError] = useState();
  
  return useCallback((error) => {
    setError(() => {
      throw error;
    });
  }, []);
}

function AsyncComponentWithHook() {
  const throwError = useAsyncError();
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetchData()
      .then(setData)
      .catch(throwError);  // 抛给错误边界
  }, [throwError]);
  
  return <div>{data}</div>;
}

// React Query自动处理异步错误
import { useQuery } from '@tanstack/react-query';

function AsyncComponentWithReactQuery() {
  const { data, error } = useQuery({
    queryKey: ['data'],
    queryFn: fetchData,
    useErrorBoundary: true  // 抛给错误边界
  });
  
  return <div>{data}</div>;
}

3.3 事件处理器错误

jsx
// 事件处理器中的错误不会被错误边界捕获
function BadEventHandler() {
  const handleClick = () => {
    throw new Error('Event handler error');  // 不会被捕获!
  };
  
  return <button onClick={handleClick}>Click Me</button>;
}

// 解决方案1: try-catch
function GoodEventHandlerSolution1() {
  const handleClick = () => {
    try {
      throw new Error('Event handler error');
    } catch (error) {
      console.error('Error in event handler:', error);
      // 显示错误UI或通知用户
    }
  };
  
  return <button onClick={handleClick}>Click Me</button>;
}

// 解决方案2: 错误状态
function GoodEventHandlerSolution2() {
  const [error, setError] = useState(null);
  
  const handleClick = () => {
    try {
      throw new Error('Event handler error');
    } catch (err) {
      setError(err);
    }
  };
  
  if (error) {
    throw error;  // 让错误边界捕获
  }
  
  return <button onClick={handleClick}>Click Me</button>;
}

// 解决方案3: 高阶函数包装
function withErrorHandling(handler) {
  return async (...args) => {
    try {
      await handler(...args);
    } catch (error) {
      console.error('Event handler error:', error);
      // 显示错误通知
      showErrorToast(error.message);
    }
  };
}

function Component() {
  const handleClick = withErrorHandling(async () => {
    await riskyOperation();
  });
  
  return <button onClick={handleClick}>Click Me</button>;
}

4. 错误日志和监控

4.1 集成错误追踪服务

jsx
// Sentry集成
import * as Sentry from '@sentry/react';

Sentry.init({
  dsn: 'YOUR_SENTRY_DSN',
  environment: process.env.NODE_ENV,
  integrations: [
    new Sentry.BrowserTracing(),
    new Sentry.Replay()
  ],
  tracesSampleRate: 1.0,
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0
});

// 使用Sentry的ErrorBoundary
function App() {
  return (
    <Sentry.ErrorBoundary
      fallback={({ error, resetError }) => (
        <div>
          <p>Error: {error.message}</p>
          <button onClick={resetError}>Try again</button>
        </div>
      )}
      showDialog
    >
      <MyApp />
    </Sentry.ErrorBoundary>
  );
}

// 手动捕获错误
function Component() {
  const handleClick = () => {
    try {
      riskyOperation();
    } catch (error) {
      Sentry.captureException(error, {
        tags: { section: 'user-action' },
        extra: { userId: getCurrentUserId() }
      });
    }
  };
  
  return <button onClick={handleClick}>Do Something</button>;
}

4.2 自定义错误日志系统

jsx
// 错误日志管理器
class ErrorLogger {
  constructor() {
    this.errors = [];
    this.maxErrors = 100;
  }
  
  log(error, errorInfo, context = {}) {
    const errorLog = {
      id: Date.now(),
      timestamp: new Date().toISOString(),
      error: {
        message: error.message,
        stack: error.stack,
        name: error.name
      },
      errorInfo,
      context: {
        url: window.location.href,
        userAgent: navigator.userAgent,
        ...context
      }
    };
    
    this.errors.push(errorLog);
    
    // 限制错误数量
    if (this.errors.length > this.maxErrors) {
      this.errors.shift();
    }
    
    // 发送到服务器
    this.sendToServer(errorLog);
    
    // 本地存储
    this.saveToLocalStorage();
  }
  
  sendToServer(errorLog) {
    fetch('/api/errors', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(errorLog)
    }).catch(err => {
      console.error('Failed to send error log:', err);
    });
  }
  
  saveToLocalStorage() {
    try {
      localStorage.setItem('errorLogs', JSON.stringify(this.errors));
    } catch (err) {
      console.error('Failed to save error logs:', err);
    }
  }
  
  getErrors() {
    return this.errors;
  }
  
  clearErrors() {
    this.errors = [];
    localStorage.removeItem('errorLogs');
  }
}

const errorLogger = new ErrorLogger();

// 在错误边界中使用
class ErrorBoundaryWithLogging extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    errorLogger.log(error, errorInfo, {
      component: this.props.componentName,
      userId: this.props.userId
    });
  }
  
  render() {
    if (this.state.hasError) {
      return <div>Error occurred</div>;
    }
    return this.props.children;
  }
}

4.3 错误分析和可视化

jsx
// 错误统计Dashboard
function ErrorDashboard() {
  const [errors, setErrors] = useState([]);
  const [stats, setStats] = useState({});
  
  useEffect(() => {
    // 获取错误日志
    const logs = errorLogger.getErrors();
    setErrors(logs);
    
    // 计算统计信息
    const statistics = {
      total: logs.length,
      byType: {},
      byHour: {},
      topErrors: {}
    };
    
    logs.forEach(log => {
      // 按类型统计
      const type = log.error.name;
      statistics.byType[type] = (statistics.byType[type] || 0) + 1;
      
      // 按小时统计
      const hour = new Date(log.timestamp).getHours();
      statistics.byHour[hour] = (statistics.byHour[hour] || 0) + 1;
      
      // Top错误
      const message = log.error.message;
      statistics.topErrors[message] = (statistics.topErrors[message] || 0) + 1;
    });
    
    setStats(statistics);
  }, []);
  
  return (
    <div>
      <h2>Error Dashboard</h2>
      
      <section>
        <h3>总体统计</h3>
        <p>总错误数: {stats.total}</p>
      </section>
      
      <section>
        <h3>按类型</h3>
        <ul>
          {Object.entries(stats.byType || {}).map(([type, count]) => (
            <li key={type}>{type}: {count}</li>
          ))}
        </ul>
      </section>
      
      <section>
        <h3>Top错误</h3>
        <ul>
          {Object.entries(stats.topErrors || {})
            .sort((a, b) => b[1] - a[1])
            .slice(0, 10)
            .map(([message, count]) => (
              <li key={message}>{message}: {count}次</li>
            ))}
        </ul>
      </section>
      
      <section>
        <h3>最近错误</h3>
        <table>
          <thead>
            <tr>
              <th>时间</th>
              <th>类型</th>
              <th>消息</th>
            </tr>
          </thead>
          <tbody>
            {errors.slice(-20).reverse().map(log => (
              <tr key={log.id}>
                <td>{new Date(log.timestamp).toLocaleString()}</td>
                <td>{log.error.name}</td>
                <td>{log.error.message}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </section>
    </div>
  );
}

5. 错误恢复策略

5.1 自动重试

jsx
class RetryErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
      retryCount: 0
    };
  }
  
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    console.error('Error:', error, errorInfo);
    
    const { maxRetries = 3, retryDelay = 1000 } = this.props;
    
    if (this.state.retryCount < maxRetries) {
      setTimeout(() => {
        this.setState(prevState => ({
          hasError: false,
          retryCount: prevState.retryCount + 1
        }));
      }, retryDelay * (this.state.retryCount + 1));  // 指数退避
    }
  }
  
  render() {
    const { hasError, retryCount } = this.state;
    const { maxRetries = 3 } = this.props;
    
    if (hasError && retryCount >= maxRetries) {
      return (
        <div>
          <p>Failed after {maxRetries} attempts</p>
          <button onClick={() => window.location.reload()}>
            Reload Page
          </button>
        </div>
      );
    }
    
    if (hasError) {
      return <div>Retrying... ({retryCount + 1}/{maxRetries})</div>;
    }
    
    return this.props.children;
  }
}

5.2 降级方案

jsx
// 优雅降级的组件
function RobustComponent({ primaryComponent: Primary, fallbackComponent: Fallback }) {
  const [useFallback, setUseFallback] = useState(false);
  
  if (useFallback && Fallback) {
    return <Fallback />;
  }
  
  return (
    <ErrorBoundary
      onError={() => setUseFallback(true)}
      fallback={Fallback ? <Fallback /> : <div>Feature unavailable</div>}
    >
      <Primary />
    </ErrorBoundary>
  );
}

// 使用
function App() {
  return (
    <RobustComponent
      primaryComponent={AdvancedChart}
      fallbackComponent={SimpleChart}
    />
  );
}

5.3 用户反馈

jsx
// 带用户反馈的错误边界
class UserFriendlyErrorBoundary extends React.Component {
  state = { hasError: false, feedbackSent: false };
  
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    this.error = error;
    this.errorInfo = errorInfo;
  }
  
  sendFeedback = (userFeedback) => {
    const errorReport = {
      error: this.error.toString(),
      errorInfo: this.errorInfo,
      userFeedback,
      timestamp: new Date().toISOString()
    };
    
    fetch('/api/error-feedback', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(errorReport)
    }).then(() => {
      this.setState({ feedbackSent: true });
    });
  };
  
  render() {
    if (this.state.hasError) {
      if (this.state.feedbackSent) {
        return (
          <div>
            <h3>Thank you for your feedback!</h3>
            <button onClick={() => window.location.reload()}>
              Reload Page
            </button>
          </div>
        );
      }
      
      return (
        <ErrorFeedbackForm
          error={this.error}
          onSubmit={this.sendFeedback}
        />
      );
    }
    
    return this.props.children;
  }
}

function ErrorFeedbackForm({ error, onSubmit }) {
  const [feedback, setFeedback] = useState('');
  
  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(feedback);
  };
  
  return (
    <div>
      <h2>Something went wrong</h2>
      <p>{error.message}</p>
      <form onSubmit={handleSubmit}>
        <label>
          What were you trying to do?
          <textarea
            value={feedback}
            onChange={e => setFeedback(e.target.value)}
            placeholder="Describe what happened..."
          />
        </label>
        <button type="submit">Send Feedback</button>
      </form>
    </div>
  );
}

6. 测试错误边界

jsx
// 测试错误边界
import { render, screen } from '@testing-library/react';

describe('ErrorBoundary', () => {
  // 抑制控制台错误
  beforeAll(() => {
    jest.spyOn(console, 'error').mockImplementation(() => {});
  });
  
  afterAll(() => {
    console.error.mockRestore();
  });
  
  it('catches errors and displays fallback UI', () => {
    const ThrowError = () => {
      throw new Error('Test error');
    };
    
    render(
      <ErrorBoundary fallback={<div>Error occurred</div>}>
        <ThrowError />
      </ErrorBoundary>
    );
    
    expect(screen.getByText('Error occurred')).toBeInTheDocument();
  });
  
  it('calls onError callback', () => {
    const onError = jest.fn();
    const ThrowError = () => {
      throw new Error('Test error');
    };
    
    render(
      <ErrorBoundary onError={onError}>
        <ThrowError />
      </ErrorBoundary>
    );
    
    expect(onError).toHaveBeenCalled();
  });
  
  it('resets error boundary', () => {
    const ThrowError = ({ shouldThrow }) => {
      if (shouldThrow) {
        throw new Error('Test error');
      }
      return <div>No error</div>;
    };
    
    const { rerender } = render(
      <ErrorBoundary>
        <ThrowError shouldThrow={true} />
      </ErrorBoundary>
    );
    
    expect(screen.getByText(/error/i)).toBeInTheDocument();
    
    // 重置错误
    rerender(
      <ErrorBoundary>
        <ThrowError shouldThrow={false} />
      </ErrorBoundary>
    );
    
    expect(screen.getByText('No error')).toBeInTheDocument();
  });
});

7. 总结

错误边界是React应用错误处理的核心机制:

  1. 基础: 使用类组件实现,提供getDerivedStateFromError和componentDidCatch
  2. 高级: 多级错误边界、可配置错误边界、错误边界工厂
  3. 最佳实践: 路由级别、异步错误、事件处理器错误
  4. 监控: Sentry集成、自定义日志、错误分析
  5. 恢复: 自动重试、降级方案、用户反馈
  6. 测试: 完整的测试覆盖

合理使用错误边界可以显著提升应用的健壮性和用户体验。