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