React

Error boundaries

Один компонент глубоко в дереве обращается к user.profile.name, а profile внезапно пришёл как null. Возникает исключение во время рендера. Без защиты React делает то, что считает безопасным: размонтирует всё дерево целиком - и пользователь видит белый экран вместо приложения. Одна опечатка в данных кладёт весь интерфейс. Error boundary существует ровно для этого: окружить часть дерева границей, которая поймает ошибку рендера и покажет аккуратный запасной UI вместо пустоты.

  • Крупные SPA: каждый независимый виджет дашборда обёрнут в свою границу, чтобы сбой одного не уронил всю страницу
  • react-error-boundary: де-факто стандартная библиотека, дающая компонент ErrorBoundary с хуками для сброса и повтора
  • Интеграции со сторонним кодом: рекламные блоки и виджеты партнёров изолируют границей, чтобы их падение не задело основной UI
  • Sentry и подобные сервисы: componentDidCatch отправляет пойманную ошибку в систему мониторинга
  • Next.js App Router: файл error.js по сути и есть error boundary вокруг сегмента маршрута

Предварительные знания

  • Понимание фаз жизни компонента: рендер, коммит в DOM, обработка событий
  • Различие синхронного кода и асинхронного (промисы, setTimeout, обработчики событий)
  • Идея дерева компонентов: родители и вложенные поддеревья

Зачем понадобились границы ошибок

До React 16 ошибка во время рендера оставляла дерево в непредсказуемом, частично обновлённом состоянии - интерфейс мог показывать мусор, а отладка превращалась в кошмар. В React 16 (2017) команда приняла жёсткое, но честное решение: если ошибку рендера никто не перехватил, всё дерево размонтируется. Лучше пустой экран, чем повреждённый и врущий UI. Чтобы дать разработчику способ перехватить такую ошибку и показать запасной UI, ввели концепцию error boundary - специальный компонент с методами getDerivedStateFromError и componentDidCatch. Эти методы существуют только в классовых компонентах: аналога-хука у них до сих пор нет, поэтому на практике часто берут библиотеку react-error-boundary, которая прячет класс за удобным API.

Что ловит граница и зачем

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

Граница перехватывает ошибки трёх категорий: в процессе рендера компонентов-потомков, в их методах жизненного цикла и в конструкторах. То есть всё, что происходит синхронно, пока React строит дерево. Это покрывает огромный класс реальных багов: обращение к полю null, неверный тип данных от API, ошибка в логике компонента.

Гранулярность - осознанный выбор. Одна граница на всё приложение защищает от белого экрана, но при любой ошибке прячет весь интерфейс. Отдельные границы вокруг независимых блоков (виджеты дашборда, секции страницы) локализуют сбой: упал один блок - остальные живут. Типичная стратегия комбинирует оба уровня.

Что произойдёт с приложением, если ошибка во время рендера не перехвачена ни одной error boundary?

Как реализовать границу

Сейчас error boundary можно создать только классовым компонентом - хук-эквивалента нет. Класс реагирует на ошибку двумя статическими и инстанс-методами. getDerivedStateFromError получает ошибку и возвращает новое состояние, которое переключает рендер на fallback. componentDidCatch вызывается после и предназначен для побочного эффекта - логирования ошибки и стека в систему мониторинга.

Поскольку писать класс каждый раз неудобно, на практике обычно берут библиотеку react-error-boundary. Она даёт готовый компонент ErrorBoundary с рендер-пропом для fallback, доступом к объекту ошибки и функции сброса, а также хуки для управления состоянием ошибки из функциональных компонентов. Это тот же механизм, но с удобным современным API.

Хороший fallback не просто сообщает о сбое, но и даёт выход: кнопку повтора, которая сбрасывает состояние границы и пытается отрендерить поддерево заново. Часто это совмещают с повторным запросом данных, чтобы повтор реально устранял причину, а не показывал ту же ошибку снова.

Зачем в классовой error boundary разделены getDerivedStateFromError и componentDidCatch?

Чего граница НЕ ловит

Самое частое заблуждение - думать, что error boundary ловит любые ошибки в поддереве. Это не так. Граница перехватывает только то, что бросается синхронно во время рендера, в методах жизненного цикла и конструкторах. Всё, что выполняется вне фазы рендера, проходит мимо: ошибки в обработчиках событий, в асинхронном коде (промисы, setTimeout, async/await), в коде серверного рендеринга на стороне самого фреймворка и ошибки внутри самой границы.

Источник ошибкиЛовит ли error boundary
Рендер компонента-потомкаДа
Метод жизненного цикла потомкаДа
Обработчик события (onClick и т.п.)Нет
Асинхронный код (промис, setTimeout)Нет
Ошибка в самой границеНет (нужна граница выше)

Причина в том, что обработчик события или колбэк промиса выполняется уже после коммита, вне дерева вызовов рендера, и React не может связать такую ошибку с конкретным местом дерева. Поэтому ошибки событий и асинхронного кода обрабатывают обычным способом - try/catch и состоянием. Если внутри обработчика надо показать тот же fallback, что и граница, состояние ошибки выставляют вручную.

Не стоит пытаться загнать всю обработку ошибок в error boundary. Сетевые сбои при сохранении, ошибки валидации, отклонённые промисы - это нормальная часть логики, которую обрабатывают локально через try/catch и состояние. Граница нужна для непредвиденных ошибок рендера, которые иначе уронили бы весь UI, а не как замена обычной обработке ожидаемых сбоев.

Кнопка вызывает асинхронный запрос в обработчике onClick, и запрос иногда падает. Поймает ли это окружающая error boundary?

Связь с другими темами

Границы ошибок - часть надёжного каркаса приложения, рядом с Suspense:

  • lazy и Suspense — Сбой загрузки ленивого чанка ловится именно error boundary, поставленной снаружи Suspense
  • Модель рендеринга — Объясняет, почему граница ловит только ошибки фазы рендера, а не события и асинхронность
  • Конкурентный рендеринг — Границы ошибок и Suspense вместе образуют каркас обработки сбоев в конкурентной модели React

Итог

  • Error boundary - компонент, перехватывающий ошибки рендера в своём поддереве и показывающий запасной UI вместо краха
  • Реализуется классовым компонентом с getDerivedStateFromError (переключить на fallback) и componentDidCatch (залогировать)
  • На практике чаще используют библиотеку react-error-boundary, которая даёт готовый компонент и средства сброса
  • Граница ловит ошибки фазы рендера, методов жизненного цикла и конструкторов детей - но НЕ ошибки обработчиков событий и асинхронного кода
  • Стратегия: глобальная граница как страховка плюс локальные границы вокруг независимых блоков, чтобы сбой одного не ронял весь экран

Связанные уроки

  • rc-10-render-mental-model — Error boundary ловит ошибки именно во время рендера, поэтому нужна модель того, когда React выполняет рендер
  • rc-21-lazy-suspense — Падение загрузки ленивого чанка - типичный случай, который ловит error boundary рядом с Suspense
  • rc-24-concurrent-intro — Границы ошибок работают в связке с Suspense внутри конкурентной модели рендеринга React
Error boundaries

0

1

Войти