Изоляция ошибок пользовательского интерфейса и обеспечение стабильности приложения.
Принципы: Изоляция сбоев позволяет приложению продолжать работу даже при падении отдельного виджета или фичи.
Виджеты, внешние компоненты, сложные UI-блоки, Lazy-loaded компоненты и интеграции (чаты, карты).
Page-level (вся страница) и Feature-level (отдельный блок: корзина, чат, аналитика).
Когда код может быть нестабилен или подключается сторонний модуль.
Базовый класс для перехвата ошибок:
1"use client";
2
3import { Component, type ErrorInfo, type ReactNode } from "react";
4
5export interface IErrorBoundaryProps {
6 children?: ReactNode;
7 fallback?: ReactNode;
8 /** Отправка в логгер / мониторинг (Sentry и т.п.), а не вывод в консоль */
9 onError?: (error: Error, errorInfo: ErrorInfo) => void;
10}
11
12interface IErrorBoundaryState {
13 hasError: boolean;
14 error: Error | null;
15}
16
17/** Fallback по умолчанию: сообщение + стек для отладки */
18const DefaultErrorUI = ({ error }: { error: Error | null }) => (
19 <div
20 role="alert"
21 className="p-4 rounded-lg flex flex-col items-start gap-2 border border-destructive/50 bg-destructive/10 text-destructive font-mono text-sm max-w-full overflow-x-auto"
22 >
23 <span className="font-bold">
24 An error occurred while rendering this component.
25 </span>
26 <details className="mt-2 w-full">
27 <summary className="cursor-pointer font-semibold opacity-80 hover:opacity-100">
28 {error?.message || "Error"}
29 </summary>
30 <pre className="mt-2 text-xs opacity-70 whitespace-pre-wrap">
31 {error?.stack}
32 </pre>
33 </details>
34 </div>
35);
36
37export class ErrorBoundary extends Component<
38 IErrorBoundaryProps,
39 IErrorBoundaryState
40> {
41 public state: IErrorBoundaryState = {
42 hasError: false,
43 error: null
44 };
45
46 public static getDerivedStateFromError(error: Error): IErrorBoundaryState {
47 return { hasError: true, error };
48 }
49
50 public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
51 this.props.onError?.(error, errorInfo);
52 }
53
54 public render() {
55 if (this.state.hasError) {
56 return this.props.fallback ?? <DefaultErrorUI error={this.state.error} />;
57 }
58 return this.props.children;
59 }
60}HOC для оборачивания компонентов:
1import React, { ComponentType } from "react";
2import { ErrorBoundary } from "./error-boundary";
3
4export function withErrorBoundary<P extends object>(
5 Component: ComponentType<P>,
6 fallback?: React.ReactNode
7) {
8 return function WithErrorBoundary(props: P) {
9 return (
10 <ErrorBoundary fallback={fallback}>
11 <Component {...props} />
12 </ErrorBoundary>
13 );
14 };
15}Практический пример изоляции фичи с помощью withErrorBoundary. Обратите внимание на то, как HOC предотвращает "белый экран" при ошибках в нестабильной логике рендеринга дочерних компонентов.
1import { withErrorBoundary } from "@/shared/ui";
2
3function UserProfileWidget({ userId }: { userId: string }) {
4 const { data, isLoading } = useGetUserProfileQuery(userId);
5
6 if (isLoading) return <SkeletonProfile />;
7
8 const formattedStats = data.stats.map(stat => ({
9 ...stat,
10 value: (stat.raw / stat.total) * 100
11 }));
12
13 return (
14 <div className="flex flex-col gap-4">
15 <UserProfileAvatar user={data.user} />
16 <UserProfileStats stats={formattedStats} />
17 </div>
18 );
19}
20
21export const UserProfile = withErrorBoundary(UserProfileWidget, <UserProfileErrorUI />);