← Назад к вопросам

Что такое Error Bundle?

2.3 Middle🔥 161 комментариев
#React#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое Error Boundary

Error Boundary (граница обработки ошибок) — это React компонент, который ловит JavaScript ошибки, возникшие в его дочерних компонентах. Он предотвращает краш всего приложения и позволяет показать fallback UI вместо белого экрана смерти.

Проблема, которую решает Error Boundary

Обычно в React, если в компоненте происходит ошибка, она пробивается вверх по дереву и весь интерфейс вломается:

function BuggyComponent() {
  // Это ошибка
  const data = null;
  return <div>{data.name}</div>; // TypeError: Cannot read property 'name'
}

function App() {
  return (
    <div>
      <Header />
      <BuggyComponent /> {/* Ошибка здесь разрушит весь App */}
      <Footer /> {/* Этот код никогда не выполнится */}
    </div>
  );
}

Это приводит к белому экрану смерти — пользователь видит пустую страницу.

Как работает Error Boundary

Error Boundary использует специальные жизненные циклы (lifecycle methods):

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  // Этот метод обновляет состояние при ошибке
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  // Этот метод логирует ошибку
  componentDidCatch(error, errorInfo) {
    console.error('Ошибка поймана:', error, errorInfo);
    // Отправить логи на сервер
    logErrorToServer({
      error: error.toString(),
      componentStack: errorInfo.componentStack
    });
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-container">
          <h1>Что-то пошло не так</h1>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false })}>
            Попробовать снова
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Использование
function App() {
  return (
    <ErrorBoundary>
      <Header />
      <BuggyComponent /> {/* Ошибка поймается */}
      <Footer />
    </ErrorBoundary>
  );
}

Когда Error Boundary ловит ошибки

Ловит (работает):

  • Ошибки в render методе
  • Ошибки в lifecycle методах
  • Ошибки в конструкторах дочерних компонентов
  • Ошибки в useLayoutEffect
function ComponentWithError() {
  // Error Boundary поймает это
  if (!data) {
    throw new Error('Data not found');
  }
  return <div>{data.name}</div>;
}

НЕ ловит:

  • Асинхронные ошибки (setTimeout, Promise)
  • Event handlers (обработчики событий)
  • Ошибки, выброшенные в самом Error Boundary
  • Server-side rendering ошибки
function ComponentWithEventError() {
  const handleClick = () => {
    // Error Boundary НЕ поймает это
    throw new Error('Click error');
  };
  
  return <button onClick={handleClick}>Клик</button>;
}

function ComponentWithAsyncError() {
  useEffect(() => {
    setTimeout(() => {
      // Error Boundary НЕ поймает это
      throw new Error('Async error');
    }, 1000);
  }, []);
  
  return <div>Компонент</div>;
}

Правильная обработка асинхронных ошибок

function ComponentWithAsyncError() {
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .catch(err => {
        // Обрабатываем ошибку вручную
        setError(err);
        // Можем также выбросить её для Error Boundary
        throw err;
      });
  }, []);

  if (error) {
    return <div>Ошибка: {error.message}</div>;
  }

  return <div>Данные загружаются...</div>;
}

Правильная обработка ошибок в event handlers

function ComponentWithEventError() {
  const [error, setError] = useState(null);

  const handleClick = () => {
    try {
      // Опасная операция
      riskyOperation();
    } catch (err) {
      // Обрабатываем ошибку вручную
      setError(err);
    }
  };

  if (error) {
    return <div>Произошла ошибка: {error.message}</div>;
  }

  return <button onClick={handleClick}>Клик</button>;
}

Практический пример с логированием

class ErrorBoundaryWithLogging extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, errorId: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // Генерируем уникальный ID для ошибки
    const errorId = Date.now();
    this.setState({ errorId });

    // Отправляем логи на Sentry, LogRocket и т.д.
    if (window.Sentry) {
      window.Sentry.captureException(error, {
        contexts: {
          react: {
            componentStack: errorInfo.componentStack,
          },
        },
      });
    }

    // Можем отправить на свой сервер
    fetch('/api/errors', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        errorId,
        message: error.toString(),
        stack: error.stack,
        componentStack: errorInfo.componentStack,
        timestamp: new Date().toISOString(),
        userAgent: navigator.userAgent,
      }),
    });
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-container">
          <h1>Упс, что-то пошло не так</h1>
          <p>
            Наша команда уже уведомлена об ошибке.
            ID ошибки: {this.state.errorId}
          </p>
          <button
            onClick={() => {
              this.setState({ hasError: false });
              window.location.href = '/';
            }}
          >
            Вернуться на главную
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Использование
function App() {
  return (
    <ErrorBoundaryWithLogging>
      <Header />
      <MainContent />
      <Footer />
    </ErrorBoundaryWithLogging>
  );
}

Несколько Error Boundaries для разных слоев

function App() {
  return (
    <ErrorBoundary>
      <Header />
      
      <ErrorBoundary fallback={<SidebarError />}>
        <Sidebar />
      </ErrorBoundary>

      <div className="main">
        <ErrorBoundary fallback={<ContentError />}>
          <MainContent />
        </ErrorBoundary>
        
        <ErrorBoundary fallback={<CommentsError />}>
          <Comments />
        </ErrorBoundary>
      </div>

      <Footer />
    </ErrorBoundary>
  );
}

Так если ошибка в Comments, упадет только Comments, остальное приложение продолжит работать.

Альтернатива: React 16.13+ (useErrorHandler hook в будущих версиях)

В будущем React планирует функциональные компоненты с обработкой ошибок:

// В будущих версиях (сейчас только в экспериментальных версиях)
function MyComponent() {
  const [error, setError] = useErrorHandler();
  // ...
}

А пока Error Boundary — это class component по необходимости.

Итог

Error Boundary нужен для:

  • Предотвращения полного краша приложения
  • Показа user-friendly сообщения об ошибке
  • Логирования ошибок на сервер
  • Частичного восстановления приложения

Error Boundary работает только с:

  • Ошибками в render
  • Ошибками в lifecycle
  • Ошибками в конструкторах

Для асинхронных ошибок и event handlers обрабатывайте ошибки вручную через try/catch или Promise.catch().