Сайт использует сookies для хранения данных. Продолжая использовать сайт, вы даёте согласие на работу с этими файлами.

ОК
⚛️
React и TypeScript
Опубликовано:
19.10.2026
•
Обновлено:
19.10.2026

Error Boundary в React: как изолировать сбой виджета и восстановить интерфейс

Тимофей Ищенко

Необработанная ошибка во время рендеринга одного второстепенного виджета может привести к полному размонтированию всего дерева React-компонентов и появлению белого экрана. Чтобы интерфейс оставался стабильным при сбоях в отдельных модулях, в React используется механизм Error Boundary (границы ошибок). Грамотно спроектированная граница отказа изолирует поврежденный компонент, отображает резервный UI (fallback), дает пользователю возможность безопасно повторить операцию и отправляет диагностику в систему мониторинга без риска утечки персональных данных.


Анатомия сбоев в React: что ловит Error Boundary, а что нет

Error Boundary не является универсальной заменой блокам try/catch. Механизм перехвата в React жестко привязан к жизненному циклу декларативного рендеринга.

Ошибки рендеринга и жизненного цикла

Error Boundary автоматически перехватывает исключения, выброшенные в следующих фазах дочернего дерева:

  • В процессе вызова функций функциональных компонентов или методов render() классовых компонентов.
  • В конструкторах дочерних компонентов.
  • В методах жизненного цикла (componentDidMount, componentDidUpdate и других).

Если ошибка произошла в одной из этих точек, React прерывает рендеринг проблемной ветки и передает управление ближайшему вышестоящему Error Boundary.

Зоны вне досягаемости границ ошибок

Error Boundary не перехватывает:

  1. Обработчики событий (onClick, onSubmit и т. д.). Обработка событий происходит вне фазы рендеринга React. Ошибки здесь не ломают отображение UI автоматически, но требуют ручной обработки через обычный try/catch с последующей записью статуса в локальное состояние компонента.
  2. Асинхронный код. Коллбэки внутри setTimeout, requestAnimationFrame или не привязанные к жизненному циклу промисы выполняются в других витках Event Loop и минуют контекст рендера React.
  3. Ошибки серверного рендеринга (SSR). Классический Error Boundary работает на клиенте во время гидратации и последующих обновлений.
  4. Ошибки внутри самой границы. Если исключение выброшено внутри самого компонента Error Boundary (а не в его children), его может поймать только более высокий предок в дереве.

Исключения в startTransition

Если внутри коллбэка startTransition выбрасывается ошибка или возвращается отклонённый Promise (rejected Promise), React связывает это исключение с фазой перехода и направляет его в ближайший Error Boundary. Это позволяет централизованно обрабатывать сбои фоновых обновлений интерфейса.


Архитектура границ отказа (Fault Tolerance) на уровне виджетов

Подход к расстановке Error Boundary определяет отказоустойчивость всей системы.

Почему глобального Error Boundary недостаточно

Если обернуть в границу ошибок только корневой компонент <App />, любая критическая ошибка во второстепенном блоке (например, в графике аналитики или виджете рекомендаций) приведет к замене всего приложения на аварийный экран. Пользователь потеряет несохраненный ввод, активные сессии и доступ к работающим частям интерфейса.

Изоляция независимых блоков интерфейса

Отказоустойчивая архитектура строится по принципу изоляции сбоев (Fault Isolation):

function DashboardPage() {
  return (
    <div className="layout">
      <Header />
      <main className="content">
        <ErrorBoundary fallback={<WidgetErrorFallback title="Основная статистика" />}>
          <MainStatsWidget />
        </ErrorBoundary>

        <ErrorBoundary fallback={<WidgetErrorFallback title="Аналитика поведения" />}>
          <ThirdPartyAnalyticsWidget />
        </ErrorBoundary>
      </main>
      <Sidebar />
    </div>
  );
}

При падении ThirdPartyAnalyticsWidget остальная часть панели управления (навигация, сайдбар, основной контент) продолжает штатно функционировать.


Реализация классового Error Boundary

На данный момент в React граница ошибок создается только с помощью классовых компонентов, так как хуков-эквивалентов для методов перехвата рендера не существует.

Разделение обязанностей: getDerivedStateFromError и componentDidCatch

Архитектура React строго разделяет вычисление резервного состояния и выполнение побочных эффектов:

  • static getDerivedStateFromError(error) — чистая функция. Срабатывает во время фазы «render». Используется исключительно для обновления состояния компонента, чтобы при следующем проходе отобразить fallback UI. Здесь запрещены сайд-эффекты.
  • componentDidCatch(error, info) — метод фазы «commit». Срабатывает после того, как интерфейс обновился. Предназначен для выполнения побочных эффектов: логирования, отправки метрик и вызова внешних коллбэков.
import React from 'react';

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

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

  componentDidCatch(error, info) {
    if (this.props.onError) {
      this.props.onError(error, info);
    }
  }

  resetError = () => {
    this.setState({ hasError: false, error: null });
  };

  render() {
    if (this.state.hasError) {
      if (typeof this.props.fallback === 'function') {
        return this.props.fallback({
          error: this.state.error,
          resetError: this.resetError
        });
      }
      return this.props.fallback || <div>Произошла ошибка при загрузке виджета.</div>;
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

Восстановление интерфейса (Reset и Retry)

После сбоя виджета пользователю необходимо предоставить способ восстановить его работоспособность без полной перезагрузки страницы.

Способ 1. Программный сброс состояния границы

Вызов метода resetError переключает состояние hasError обратно в false. Если сбой был вызван временной сетевой ошибкой или изменившимися внешними пропсами, компонент успешно отрендерится заново.

Способ 2. Полный перемонтаж через key (Remounting)

Если дочерний компонент в момент сбоя повредил свое внутреннее состояние (state хуков или классов), простой сброс флага границы может снова вызвать ту же ошибку. Для гарантированной очистки внутреннего состояния дочернего дерева используется паттерн изменения ключа key:

import React, { useState } from 'react';

function AnalyticsWidgetSlot() {
  const [retryKey, setRetryKey] = useState(0);

  const handleRetry = () => {
    // Изменение ключа принудительно перемонтирует всё поддерево с нуля
    setRetryKey((prev) => prev + 1);
  };

  return (
    <ErrorBoundary
      key={retryKey}
      fallback={({ resetError }) => (
        <div className="widget-fallback">
          <p>Не удалось отобразить данные виджета.</p>
          <button
            onClick={() => {
              resetError();
              handleRetry();
            }}
          >
            Повторить попытку
          </button>
        </div>
      )}
    >
      <AnalyticsWidget />
    </ErrorBoundary>
  );
}

Диагностика без утечки данных: безопасное логирование ошибок

Параметр info в методе componentDidCatch содержит свойство componentStack — трассировку дерева компонентов, в котором произошел сбой.

Особенности componentStack в Production

В среде разработки componentStack содержит понятные имена компонентов и пути к файлам. В production-сборке из-за минификации и обфускации имена компонентов сокращаются до одной-двух букв (например, at a (at b)). Для корректного разбора таких стеков на стороне мониторинга требуется загрузка source maps в закрытый контур системы логирования.

Фильтрация и санитизация Payload (защита PII)

Объекты ошибок (Error) и стек вызовов могут случайно содержать конфиденциальные данные пользователей:

  • Токены авторизации и пароли, случайно переданные в свойства компонентов или текст ошибки.
  • Персональные данные (имена, email, номера карт, телефоны), отображавшиеся в момент сбоя.

Перед отправкой диагностического события в систему телеметрии необходимо проводить санитизацию:

function sanitizeComponentStack(stack) {
  if (!stack) return '';
  // Удаление возможных query-параметров и чувствительных строк из путей
  return stack.replace(/([?&][a-zA-Z0-9_]+)=[^&\s]+/g, '$1=[REDACTED]');
}

function reportDiagnostic(error, info) {
  const payload = {
    eventType: 'ui_render_error',
    errorName: error?.name || 'UnknownError',
    errorMessage: error?.message?.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '[EMAIL_REDACTED]'),
    componentStack: sanitizeComponentStack(info?.componentStack),
    timestamp: new Date().toISOString()
  };

  if (navigator.sendBeacon) {
    navigator.sendBeacon('/api/diagnostics', JSON.stringify(payload));
  } else {
    fetch('/api/diagnostics', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
      keepalive: true
    }).catch(() => {
      // Предотвращаем падение при сбое отправки логов
    });
  }
}

Чек-лист: проектирование надежного виджета с защитой от сбоев

  1. Гранулярность границ. Изолированы все независимые блоки, особенно сторонние SDK, виджеты карт, графиков и динамических отчетов.
  2. Чистота методов. Метод getDerivedStateFromError выполняет только синхронный возврат нового состояния. Все вызовы аналитики вынесены в componentDidCatch.
  3. Безопасный Fallback. Резервный UI максимально прост и не содержит сложной логики, которая сама способна выбросить исключение.
  4. Сброс и перемонтаж. Предусмотрена кнопка повторной попытки, сбрасывающая локальное состояние и перемонтирующая дочерние компоненты через смену ключа (key).
  5. Обработка асинхронных операций. Внутри самого виджета вызовы API и события кликов обернуты в try/catch с выводом ошибки в локальное состояние виджета, не надеясь на Error Boundary.
  6. Санитизация логов. Перед отправкой телеметрии удалены email-адреса, токены и приватные параметры из error.message и componentStack.

Часто задаваемые вопросы (FAQ)

Почему в React до сих пор нет хука для Error Boundary?

Механизм перехвата ошибок глубоко интегрирован в фазы рендеринга классовых компонентов. Команда React пока не представила хук-аналог, так как хуки вызываются во время рендера функционального компонента и не имеют отдельного контекста для перехвата исключений собственных дочерних узлов без классовой обертки.

Как перехватить ошибку внутри события onClick и отобразить Fallback через Error Boundary?

Error Boundary не перехватывает ошибки событий напрямую. Чтобы активировать Error Boundary из обработчика событий, можно обернуть изменение состояния в startTransition (если операция асинхронная) либо записать ошибку в локальное состояние компонента и выбросить ее во время фазы рендера:

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

if (error) {
  throw error; // Попадет в ближайший Error Boundary
}

const handleClick = async () => {
  try {
    await sendData();
  } catch (err) {
    setError(err);
  }
};

Что произойдет, если ошибка возникнет внутри самого Fallback-компонента?

Она будет проброшена выше по дереву компонентов до следующего родительского Error Boundary. Если других границ выше нет, все приложение размонтируется. Поэтому компоненты-заглушки должны быть максимально простыми и статичными.

Почему в production-сборке componentStack не содержит реальных названий компонентов?

При сборке для production код проходит процесс минификации (Terser, SWC, esbuild), который заменяет имена функций и классов на короткие идентификаторы для оптимизации размера бандла. Чтобы восстановить читаемые названия компонентов, системы мониторинга используют Source Maps.

Стоит ли оборачивать абсолютно каждый компонент в Error Boundary?

Нет. Чрезмерная гранулярность усложняет структуру приложения и поддержку кода. Оборачивать следует крупные архитектурные блоки: страницы целиком, независимые виджеты, панели управления, сложные аналитические модули и интеграции со сторонними библиотеками.


Заключение

Error Boundary — фундамент создания отказоустойчивых React-приложений. Изолируя независимые виджеты на уровне границ отказа, вы предотвращаете аварийное закрытие всего приложения из-за второстепенного сбоя. Сочетание паттерна сброса ключа (key), продуманного Fallback UI и безопасной санитизации диагностических логов позволяет создавать интерфейсы, которые сохраняют данные пользователя и восстанавливают работу в один клик.

Источники

Это авторская статья, основанная на личном опыте и субъективном взгляде автора. Заметили ошибку или битую ссылку? Сообщите нам: info@codesrc.ru - мы оперативно исправим. Спасибо, что помогаете делать блог лучше.
Следите за нами в соцсетях:

Читайте также