Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое 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().