Пошук уроків, статей та іншого контенту
Створимо Error Boundary для перехоплення помилок під час рендерингу та показу резервного інтерфейсу.
Error Boundary — це спеціальний React-компонент, який перехоплює помилки під час:
рендерингу дочірніх компонентів;
виконання методів життєвого циклу;
виконання конструкторів дочірніх компонентів.
Замість того щоб уся частина інтерфейсу перестала працювати, Error Boundary може показати резервний інтерфейс:
Не вдалося завантажити цей блок.
Спробувати ще разError Boundary не перехоплює помилки:
у обробниках подій;
в асинхронному коді, наприклад у setTimeout або fetch;
під час серверного рендерингу;
у самому Error Boundary.
Error Boundary має бути класовим компонентом і реалізувати один або обидва методи:
static getDerivedStateFromError;
componentDidCatch.
getDerivedStateFromError оновлює стан компонента, щоб показати резервний інтерфейс.
componentDidCatch зазвичай використовують для логування помилки.
import React from "react";
class ErrorBoundary extends React.Component {
state = {
hasError: false,
error: null,
};
static getDerivedStateFromError(error) {
return {
hasError: true,
error,
};
}
componentDidCatch(error, errorInfo) {
console.error("Помилка в дочірньому компоненті:", error);
console.error("Інформація про компонент:", errorInfo);
}
reset = () => {
this.setState({
hasError: false,
error: null,
});
};
render() {
if (this.state.hasError) {
return this.props.fallback({
error: this.state.error,
reset: this.reset,
});
}
return this.props.children;
}
}
function BrokenWidget({ shouldCrash }) {
if (shouldCrash) {
throw new Error("Віджет не може бути відрендерений");
}
return <p>Віджет працює правильно.</p>;
}
function App() {
const [shouldCrash, setShouldCrash] = React.useState(false);
return (
<main>
<h1>Сторінка профілю</h1>
<button onClick={() => setShouldCrash(true)}>
Зламати віджет
</button>
<ErrorBoundary
fallback={({ error, reset }) => (
<section role="alert">
<h2>Не вдалося показати віджет</h2>
<p>{error.message}</p>
<button
onClick={() => {
setShouldCrash(false);
reset();
}}
>
Спробувати ще раз
</button>
</section>
)}
>
<BrokenWidget shouldCrash={shouldCrash} />
</ErrorBoundary>
</main>
);
}
export default App;У цьому прикладі:
BrokenWidget навмисно викидає помилку під час рендерингу.
ErrorBoundary перехоплює цю помилку.
React викликає getDerivedStateFromError.
Замість BrokenWidget відображається fallback.
Кнопка скидає стан помилки та повертає звичайний інтерфейс.
getDerivedStateFromErrorЦей статичний метод отримує помилку та повертає новий стан:
static getDerivedStateFromError(error) {
return {
hasError: true,
error,
};
}Після зміни стану React повторно виконує render. Оскільки hasError дорівнює true, компонент показує резервний інтерфейс.
Метод не повинен виконувати побічні ефекти, наприклад надсилати дані на сервер. Для цього підходить componentDidCatch.
componentDidCatchМетод отримує два аргументи:
саму помилку;
об’єкт errorInfo з інформацією про дерево компонентів.
componentDidCatch(error, errorInfo) {
console.error(error);
console.error(errorInfo.componentStack);
}У реальному застосунку тут можна передати інформацію про помилку до системи моніторингу. Не слід покладатися лише на показ повідомлення користувачу: помилку також важливо записати для подальшого аналізу.
Error Boundary перехоплює помилки лише у своїх дочірніх компонентах:
<ErrorBoundary fallback={Fallback}>
<Profile />
<Notifications />
</ErrorBoundary>Якщо помилка виникла в Profile, boundary покаже резервний інтерфейс для всього вмісту всередині нього.
Сам Error Boundary не може перехопити власну помилку:
class ErrorBoundary extends React.Component {
render() {
throw new Error("Помилка всередині boundary");
}
}У такій ситуації потрібен інший Error Boundary, розташований вище в дереві компонентів.
Один boundary навколо всієї програми може приховати весь інтерфейс через помилку в одному невеликому компоненті:
<ErrorBoundary fallback={<FullPageError />}>
<App />
</ErrorBoundary>Іноді краще розмістити boundaries окремо для великих незалежних частин:
<>
<Header />
<ErrorBoundary fallback={<ProfileError />}>
<Profile />
</ErrorBoundary>
<ErrorBoundary fallback={<NewsError />}>
<NewsFeed />
</ErrorBoundary>
</>Тоді помилка у стрічці новин не зламає профіль користувача.
Розташовуйте boundary навколо функціонально самостійного блоку:
форми;
панелі статистики;
редактора;
віджета;
окремого маршруту або сторінки.
Error Boundary не перехоплює помилки, які виникли в обробнику події:
function Button() {
function handleClick() {
throw new Error("Помилка під час натискання");
}
return <button onClick={handleClick}>Натиснути</button>;
}Такі помилки потрібно обробляти безпосередньо в обробнику:
function Button() {
function handleClick() {
try {
performAction();
} catch (error) {
console.error("Не вдалося виконати дію:", error);
}
}
return <button onClick={handleClick}>Натиснути</button>;
}
function performAction() {
throw new Error("Операція завершилася помилкою");
}Для асинхронних операцій також потрібно використовувати власний try...catch або обробляти стан помилки:
async function loadData() {
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error("Помилка HTTP-запиту");
}
return await response.json();
} catch (error) {
console.error("Не вдалося завантажити дані:", error);
throw error;
}
}try...catch навколо JSXТакий код не перехоплює помилки дочірнього рендерингу:
function App() {
try {
return <Profile />;
} catch (error) {
return <p>Помилка</p>;
}
}React виконує рендеринг компонентів у власному процесі, тому для цього потрібен саме Error Boundary.
fetchError Boundary не стежить за асинхронними операціями. Результат запиту потрібно зберігати у стані компонента та окремо показувати стан помилки.
Якщо резервний інтерфейс не має кнопки повторної спроби або іншого способу відновлення, користувач може залишитися на екрані помилки.
Водночас простого скидання стану boundary недостатньо, якщо дочірній компонент знову одразу викидає ту саму помилку. Потрібно також виправити або змінити дані, які спричинили помилку.
Якщо один boundary обгортає всю програму, помилка невеликого віджета може приховати весь застосунок. Для великих незалежних частин інтерфейсу краще створювати окремі boundaries.
Error Boundary перехоплює помилки під час рендерингу дочірніх компонентів.
Для створення boundary використовують класовий компонент.
getDerivedStateFromError змінює стан і вмикає резервний інтерфейс.
componentDidCatch підходить для логування помилок.
Error Boundary не перехоплює помилки в обробниках подій і асинхронному коді.
Розміщуйте boundaries навколо незалежних частин інтерфейсу.
Для відновлення потрібно скинути стан помилки та усунути причину повторного падіння.