ФАЗА 38. ДАННЫЕ И СОСТОЯНИЕ Ошибки

Error Boundary и состояния данных изолируют сбой

Fallback · Retry · Suspense

Ошибка рендера перехватывается границей, ошибка запроса показывается как состояние с повтором, Suspense декларирует ожидание.

1. Ошибка рендера и ошибка данных различаются

1 / 5

Исключение компонента относится к дереву React, отказ API является ожидаемым состоянием ресурса и не требует разрушения экрана.

  • Render exception перехватывается.
  • Network error обрабатывается локально.
  • Событие не ловится Error Boundary.
Ошибка рендераОшибка данных

2. Error Boundary заменяет сломанную ветку fallback

2 / 5

Классовый boundary получает ошибку потомка и показывает запасной UI вместо падения всего приложения.

  • getDerivedStateFromError меняет fallback.
  • componentDidCatch журналирует.
  • Reset создаёт новую попытку.
Ошибка потомкаError BoundaryFallback

3. Запрос сохраняет ошибку и действие повтора

3 / 5

Экран получает понятное сообщение и кнопку retry, которая повторяет ресурс без перезагрузки всей страницы.

  • Ошибка не скрывается пустым списком.
  • Loading повтора отличается.
  • Технические детали не раскрываются.
Ошибка запросаСообщениеRetry

4. Suspense объявляет состояние ожидания

4 / 5

Ближайший Suspense boundary показывает fallback, пока поддерживаемая операция не готова, затем отображает содержимое.

  • Загрузка локализована.
  • Fallback сохраняет структуру.
  • Error Boundary сочетается с Suspense.
Error BoundarySuspense fallbackГотовый контент

5. Границы ставятся по независимым зонам

5 / 5

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

  • Зона имеет собственный fallback.
  • Ошибка журналируется.
  • Reset восстанавливает корректные входы.
СтраницаОсновной контентБоковая панельНезависимый fallback

Обработка ошибок во frontend: error boundary, состояния и Suspense

← назад к разделу

Вы открываете страницу, и вместо контента — пустой белый экран. Приложение упало. Чаще всего причина — одна небольшая ошибка в одном месте, которая утащила за собой всё остальное. Это предотвратимо.

Разберём с нуля: почему так происходит, какие бывают виды ошибок во frontend, и как правильно их перехватывать.

Почему одна ошибка роняет всё приложение

React рендерит компоненты по дереву. Если где-то в этом дереве выбрасывается исключение — например, пришли данные без нужного поля, и код пытается прочитать undefined.name — React не знает, что делать, и по умолчанию размонтирует всё дерево целиком. Пользователь видит белый экран.

Решение — явно указать React: «если в этом месте что-то упало, покажи запасной экран, но не трогай остальное». Для этого существует error boundary.

Ошибки бывают двух видов

Прежде чем выбирать инструмент, важно понять природу ошибки:

Сбой рендера — исключение прямо в коде компонента: обратились к undefined, сломалась логика вычисления. Такие ошибки перехватывает error boundary.

Ошибка данных — запрос к серверу вернул ошибку (сеть упала, сервер ответил 500). Это не исключение рендера — это ожидаемое состояние, которое обрабатывается отдельно, через состояние компонента.

Путать их — частая причина, по которой «обработка ошибок не работает»: поставили boundary, ждёте, что он поймает ошибку запроса, — но он её не поймает.

Error boundary: ловим сбои рендера

Error boundary — это компонент-обёртка, который перехватывает исключения в своём поддереве и показывает запасной экран вместо падения. React пока не добавил хук для этого, поэтому boundary пишут классовым компонентом — или берут готовую библиотеку react-error-boundary.

Минимальная реализация:

class ErrorBoundary extends React.Component<
  { fallback: React.ReactNode; children: React.ReactNode },
  { hasError: boolean }
> {
  state = { hasError: false };

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

  componentDidCatch(error: unknown) {
    // здесь можно отправить в систему мониторинга
  }

  render() {
    return this.state.hasError ? this.props.fallback : this.props.children;
  }
}

Использование:

<ErrorBoundary fallback={<ErrorScreen />}>
  <Dashboard />
</ErrorBoundary>

Если Dashboard или любой компонент внутри выбросит исключение, пользователь увидит <ErrorScreen />, а не белый экран.

Ошибки данных: состояния вместо boundary

Правильный подход: запрос возвращает состояние isError, и компонент явно его обрабатывает.

Если вы используете TanStack Query, это выглядит так:

function Products() {
  const { data, isPending, isError, refetch } = useProducts();

  if (isPending) return <Spinner />;
  if (isError) return <ErrorBox onRetry={() => refetch()} />;

  return <ProductList products={data} />;
}

Ключевое: ошибка сети — это не катастрофа, а ожидаемое состояние. Сеть моргнула — показали сообщение и кнопку «повторить». Пользователь понимает, что происходит, и может что-то сделать.

Suspense: declarative loading-состояние

Suspense решает другую проблему — показ загрузки. Вместо того чтобы писать if (isPending) return <Spinner /> в каждом компоненте, вы оборачиваете поддерево и задаёте один общий fallback:

<Suspense fallback={<Spinner />}>
  <LazyDashboard />
</Suspense>

Пока компоненты внутри «приостановлены» (загружаются), Suspense показывает fallback. Это же работает для ленивой загрузки компонентов через React.lazy.

Suspense и error boundary часто ставят рядом — они дополняют друг друга: первый отвечает за «грузится», второй за «упало».

<ErrorBoundary fallback={<WidgetError />}>
  <Suspense fallback={<Spinner />}>
    <LazyWidget />
  </Suspense>
</ErrorBoundary>
что случилось error boundary зоны сбой рендера: undefined.name isError и кнопка повтора ошибка данных: сеть, 500 Suspense с fallback ещё грузится

Три разных инструмента на три разных события; boundary ошибку запроса не увидит, потому что это не исключение рендера.

Изоляция по зонам: не один boundary на всё

Ставить один error boundary на корень приложения — недостаточно. Если он сработает, пользователь потеряет весь интерфейс.

Правильнее разбить экран на независимые зоны и обернуть каждую важную зону своим boundary. Упала одна зона — остальные продолжают работать.

<Page>
  <ErrorBoundary fallback={<WidgetError name="Рекомендации" />}>
    <Recommendations />
  </ErrorBoundary>

  <ErrorBoundary fallback={<WidgetError name="Последние заказы" />}>
    <RecentOrders />
  </ErrorBoundary>
</Page>

Рекомендации упали — заказы и навигация живут. Пользователь продолжает работать с продуктом. Это и называют graceful degradation: продукт остаётся полезным, даже когда часть его сломалась.

Хороший fallback — не просто «что-то пошло не так», а понятное сообщение с возможностью попробовать ещё раз.

Коротко

  • Необработанное исключение в компоненте по умолчанию роняет всё дерево React — пользователь видит белый экран.
  • Ошибки бывают двух видов: сбой рендера (ловит error boundary) и ошибка данных (обрабатывается состоянием).
  • Error boundary — компонент-обёртка с getDerivedStateFromError, перехватывает исключения в поддереве и показывает запасной экран.
  • Ошибку HTTP-запроса boundary не поймает — обрабатывайте через состояние (isError) с кнопкой повтора.
  • Suspense — декларативная граница загрузки; часто ставится рядом с boundary (один за «грузится», другой за «упало»).
  • Boundary ставят по зонам, не один на всё приложение — чтобы падение виджета не уносило страницу целиком.
  • Хороший fallback содержит понятное сообщение и возможность повторить действие.

Что почитать дальше