Как используются HOC для обработки ошибок?
Представьте, что ваше React-приложение — это большой офис, полный разных сотрудников (компонентов). Каждый из сотрудников решает свою задачу, но иногда кто-то ошибается. Чтобы не закрывать весь офис из-за ошибок одного сотрудника, нам нужен метод для перехвата таких ситуаций. Именно этим и займется наш HOC.
HOC для обработки ошибок позволяет оборачивать компоненты и добавлять им интерфейс обработки исключений. Если внутри компонента что-то пойдет не так (например, ошибка JavaScript или ошибка API), этот HOC сможет поймать ошибку, показать дружелюбное сообщение пользователю и при необходимости отправить данные об ошибке в систему мониторинга.
Основы создания HOC
Начнем с создания простого компонента высшего порядка, который поможет нам обработать ошибки. Мы будем использовать встроенный метод React под названием componentDidCatch, который доступен в Error Boundary (границе ошибок). Помните, что Error Boundary работает только с ошибками в процессе рендера, в методах жизненного цикла и конструкторах дочерних компонентов.
Базовая реализация HOC
Давайте создадим HOC, который добавляет функциональность обработки ошибок:
import React, { Component, ComponentType } from 'react';
// Интерфейс для состояния нашего HOC
interface ErrorBoundaryState {
hasError: boolean;
errorMessage: string;
}
// Функция HOC принимает компонент и возвращает новый компонент с обработкой ошибок
function withErrorBoundary<P>(WrappedComponent: ComponentType<P>) {
return class ErrorBoundary extends Component<P, ErrorBoundaryState> {
constructor(props: P) {
super(props);
this.state = {
hasError: false,
errorMessage: '',
};
}
// Метод ловит ошибки в дочерних компонентах
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error("Caught error:", error, errorInfo);
this.setState({ hasError: true, errorMessage: error.message });
}
render() {
const { hasError, errorMessage } = this.state;
if (hasError) {
// Показываем пользовательский интерфейс для обработки ошибки
return <h1>Что-то пошло не так: {errorMessage}</h1>;
}
// Если ошибок нет, рендерим оборачиваемый компонент
return <WrappedComponent {...this.props} />;
}
};
}
export default withErrorBoundary;
Разберем код
- Типизация пропсов: мы принимаем компонент с неизвестными пропсами
P(generic тип), чтобы наш HOC был универсальным. - Состояние HOC: состояние хранит флаг
hasErrorи сообщение об ошибке. Это позволяет нам отображать ошибку при её возникновении. - Метод
componentDidCatch: этот метод вызывает React в случае ошибки в дочернем компоненте. Мы используем его для обновления состояния и логгирования ошибки в консоли. - Рендеринг: если ошибка произошла, отображается сообщение; если нет, оборачиваемый компонент рендерится как обычно.
Пример использования HOC
Теперь давайте посмотрим, как использовать наш HOC. Создадим компонент, который генерирует ошибку, и обернем его в HOC.
Компонент с ошибкой
const BuggyComponent: React.FC = () => {
throw new Error("Ой! Что-то пошло не так...");
return <div>Этот текст вы никогда не увидите</div>;
};
Оборачиваем компонент
import React from 'react';
import withErrorBoundary from './withErrorBoundary';
const SafeComponent = withErrorBoundary(BuggyComponent);
const App: React.FC = () => {
return (
<div>
<h1>Пример обработки ошибок</h1>
<SafeComponent />
</div>
);
};
export default App;
Когда вы запустите приложение, вместо того чтобы увидеть экран смерти, вы получите пользовательское сообщение: "Что-то пошло не так...".
Улучшаем HOC: передача пользовательского интерфейса
Иногда мы хотим, чтобы при ошибке отображалось не дефолтное сообщение, а кастомный компонент (например, кнопка "Попробовать снова" или сообщение о техобслуживании).
Обновляем HOC
Добавим возможность передавать компонент для отображения ошибок:
interface ErrorBoundaryProps {
fallback?: React.ReactNode;
}
function withErrorBoundary<P>(
WrappedComponent: ComponentType<P>,
options?: ErrorBoundaryProps
) {
return class ErrorBoundary extends Component<P, ErrorBoundaryState> {
constructor(props: P) {
super(props);
this.state = {
hasError: false,
errorMessage: '',
};
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error("Caught error:", error, errorInfo);
this.setState({ hasError: true, errorMessage: error.message });
}
render() {
const { hasError, errorMessage } = this.state;
if (hasError) {
return options?.fallback || <h1>Что-то пошло не так: {errorMessage}</h1>;
}
return <WrappedComponent {...this.props} />;
}
};
}
Как использовать?
Теперь мы можем передать компонент для отображения ошибок:
const CustomErrorFallback = () => (
<div style={{ color: 'red' }}>
<h2>Oops! Это ошибка.</h2>
<button onClick={() => window.location.reload()}>Перезагрузить страницу</button>
</div>
);
const SafeComponentWithCustomFallback = withErrorBoundary(BuggyComponent, {
fallback: <CustomErrorFallback />,
});
Типичные ошибки и особенности
При создании HOC для обработки ошибок необходимо помнить несколько важных моментов:
- Error Boundary не ловит ошибки в обработчиках событий, асинхронных функциях или цепочках промисов. Чтобы поймать такие ошибки, используйте
try-catchв асинхронных функциях или добавляйте обработчики ошибок на уровне Promise. - Если вы передаете большой объем пропсов, убедитесь, что их типизация корректно обрабатывается. Используя
Pкак generic тип, вы минимизируете риск ошибок. - Никогда не злоупотребляйте Error Boundary. Это инструмент для улучшения пользовательского опыта, а не для "маскировки" проблем в вашем коде.
Этот HOC можно использовать для создания более надежных приложений. Например, вы можете оборачивать компоненты, которые работают с данными из API. Если произойдет ошибка, пользователь увидит понятное сообщение вместо "крутящегося колеса" до бесконечности. Подобные компоненты помогут вам развить привычку писать приложения, которые "не боятся" проблем и умеют их элегантно обрабатывать.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ