Границі помилок у React
Що таке Error Boundaries?
Error Boundaries — це компоненти React, які ловлять помилки JavaScript у дереві компонентів на своїх дочірніх компонентх, записують їх у лог та відображають резервний інтерфейс замість того, щоб зламати всю програму.
Чому варто використовувати Error Boundaries?
До появи Error Boundaries, помилка в одному компоненті могла зламати всю програму. Error Boundaries дозволяють вам:
- Запобігти зламу всієї програми
- Показати користувачам дружнє повідомлення про помилку
- Записувати помилки для аналізу
- Ізолювати проблемні частини програми
Створення Error Boundary
Error Boundary — це класовий компонент з методами getDerivedStateFromError та/або componentDidCatch.
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Оновити стан, щоб показати резервний інтерфейс
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Записати помилку в сервіс звітності
console.error('Помилка, спіймана кордоном:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Щось пішло не так.</h1>;
}
return this.props.children;
}
}Використання
Обгорніть компоненти, які можуть викликати помилки:
function App() {
return (
<div>
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
<ErrorBoundary>
<AnotherWidget />
</ErrorBoundary>
</div>
);
}Якщо MyWidget викликає помилку, тільки він зламається, тоді як AnotherWidget продовжить працювати.
Різниця між методами
getDerivedStateFromError
Викликається під час фази рендерингу. Використовується для оновлення стану та показу резервного інтерфейсу.
static getDerivedStateFromError(error) {
// Потрібно повернути об'єкт для оновлення стану
return { hasError: true, error };
}componentDidCatch
Викликається після рендерингу. Використовується для запису помилок.
componentDidCatch(error, errorInfo) {
// errorInfo.componentStack містить стек викликів компонентів
logErrorToService(error, errorInfo);
}Розширений приклад
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
this.setState({
error,
errorInfo
});
// Відправити в сервіс моніторингу
logErrorToService(error, errorInfo);
}
handleReset = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null
});
};
render() {
if (this.state.hasError) {
return (
<div className="error-container">
<h2Виникла помилка</h2>
<details>
<summary>Деталі</summary>
<p>{this.state.error && this.state.error.toString()}</p>
<pre>{this.state.errorInfo && this.state.errorInfo.componentStack}</pre>
</details>
<button onClick={this.handleReset}>
Спробувати ще раз
</button>
</div>
);
}
return this.props.children;
}
}Що не ловлять Error Boundaries
Error Boundaries ловлять помилки в:
- Методах рендерингу
- Життєвих циклах
- Конструкторах дочірніх компонентів
Вони НЕ ловлять:
- Обробники подій
- Асинхронний код (setTimeout, Promise)
- Помилки на серверній стороні
- Помилки в самому Error Boundary
Обробники подій
Використовуйте звичайний try-catch для обробників подій:
function MyComponent() {
function handleClick() {
try {
// Код, який може викликати помилку
somethingDangerous();
} catch (error) {
console.error('Помилка в обробнику кліків:', error);
}
}
return <button onClick={handleClick}>Натисни на мене</button>;
}Асинхронні помилки
Використовуйте try-catch для асинхронних помилок також:
function MyComponent() {
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
// ...
} catch (error) {
console.error('Помилка при отриманні:', error);
}
}
useEffect(() => {
fetchData();
}, []);
return <div>...</div>;
}Де розміщувати Error Boundaries
Глобальний рівень
function App() {
return (
<ErrorBoundary>
<Router>
<Layout>
<Routes />
</Layout>
</Router>
</ErrorBoundary>
);
}Рівень маршруту
function App() {
return (
<Router>
<ErrorBoundary>
<Route path="/profile" element={<Profile />} />
</ErrorBoundary>
<ErrorBoundary>
<Route path="/settings" element={<Settings />} />
</ErrorBoundary>
</Router>
);
}Рівень компонент
function Dashboard() {
return (
<div>
<ErrorBoundary>
<ChartWidget />
</ErrorBoundary>
<ErrorBoundary>
<StatsWidget />
</ErrorBoundary>
</div>
);
}Інтеграція з сервісами моніторингу
Sentry
import * as Sentry from '@sentry/react';
class ErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
Sentry.captureException(error, { contexts: { react: errorInfo } });
}
// ...
}
// Або використовуйте вбудований ErrorBoundary від Sentry
import { ErrorBoundary } from '@sentry/react';
function App() {
return (
<ErrorBoundary fallback={<ErrorFallback />}>
<MyApp />
</ErrorBoundary>
);
}React 18 та useErrorBoundary
React ще не має хуку для Error Boundaries, але бібліотеки пропонують рішення:
react-error-boundary
import { ErrorBoundary, useErrorHandler } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Щось пішло не так:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Спробувати ще раз</button>
</div>
);
}
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// Скинути стан програми
}}
onError={(error, errorInfo) => {
// Записати помилку
}}
>
<MyApp />
</ErrorBoundary>
);
}
// Використовуйте в функціональних компонентх
function MyComponent() {
const handleError = useErrorHandler();
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('Не вдалося отримати');
return response.json();
} catch (error) {
handleError(error);
}
}
// ...
}Кращі практики
Кілька Error Boundaries
Не використовуйте один глобальний Error Boundary. Використовуйте кілька на різних рівнях:
function App() {
return (
<ErrorBoundary fallback={<GlobalError />}>
<Header />
<ErrorBoundary fallback={<SidebarError />}>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary fallback={<ContentError />}>
<Content />
</ErrorBoundary>
<Footer />
</ErrorBoundary>
);
}Інформативні повідомлення
Показуйте користувачам корисну інформацію:
function ErrorFallback({ error }) {
return (
<div className="error-page">
<h1>Упс! Щось пішло не так</h1>
<p>Ми працюємо над виправленням цього.</p>
<p>Спробуйте:</p>
<ul>
<li>Оновити сторінку</li>
<li>Повернутися на головну</li>
<li>Повідомити нас про проблему</li>
</ul>
{process.env.NODE_ENV === 'development' && (
<details>
<summary>Технічні деталі</summary>
<pre>{error.message}</pre>
</details>
)}
</div>
);
}Варіанти відновлення
Дайте користувачам можливість спробувати ще раз:
class ErrorBoundary extends React.Component {
state = { hasError: false, errorCount: 0 };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidUpdate(prevProps) {
// Скинути помилку, коли діти змінюються
if (this.state.hasError && prevProps.children !== this.props.children) {
this.setState({ hasError: false, errorCount: 0 });
}
}
handleReset = () => {
this.setState(state => ({
hasError: false,
errorCount: state.errorCount + 1
}));
};
render() {
if (this.state.hasError) {
return (
<div>
<h1>Помилка</h1>
<button onClick={this.handleReset}>Спробувати ще раз</button>
{this.state.errorCount > 2 && (
<p>Якщо проблема не зникне, будь ласка, зверніться до служби підтримки.</p>
)}
</div>
);
}
return this.props.children;
}
}Висновок
Error Boundaries:
- Ловлять помилки в компонентх React
- Має бути класовими компонентми
- Використовують
getDerivedStateFromErrorтаcomponentDidCatch - Не ловлять помилки в обробниках подій та асинхронному коді
- Найкраще використовувати кілька на різних рівнях
- Дозволяють показувати резервний інтерфейс замість порожнього екрану
- Важливі для виробничих застосунків
На співбесідах:
Важливо вміти:
- Пояснити, що таке Error Boundaries
- Показати, як створити Error Boundary
- Описати, які помилки вони не ловлять
- Пояснити різницю між
getDerivedStateFromErrorтаcomponentDidCatch - Навести приклади правильного розміщення Error Boundary
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.