Изоляция ошибок пользовательского интерфейса и обеспечение стабильности приложения.
Принципы: Изоляция сбоев позволяет приложению продолжать работу даже при падении отдельного виджета или фичи.
Виджеты, внешние компоненты, сложные UI-блоки, Lazy-loaded компоненты и интеграции (чаты, карты).
Page-level (вся страница) и Feature-level (отдельный блок: корзина, чат, аналитика).
Когда код может быть нестабилен или подключается сторонний модуль.
Базовый класс для перехвата ошибок:
1"use client";
2
3import React, { Component, ErrorInfo, ReactNode } from "react";
4
5interface Props {
6 children?: ReactNode;
7 fallback?: ReactNode;
8}
9
10interface State {
11 hasError: boolean;
12 error: Error | null;
13}
14
15export class ErrorBoundary extends Component<Props, State> {
16 public state: State = {
17 hasError: false,
18 error: null
19 };
20
21 public static getDerivedStateFromError(error: Error): State {
22 return { hasError: true, error };
23 }
24
25 public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
26 console.error("ErrorBoundary caught an error:", error, errorInfo);
27 }
28
29 public render() {
30 if (this.state.hasError) {
31 return (
32 this.props.fallback || (
33 <div 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">
34 <span className="font-bold flex items-center gap-2">
35 ⚠️ An error occurred while rendering this component.
36 </span>
37 <details className="mt-2 w-full">
38 <summary className="cursor-pointer font-semibold opacity-80 hover:opacity-100">
39 {this.state.error?.message || "Error"}
40 </summary>
41 <pre className="mt-2 text-xs opacity-70 whitespace-pre-wrap">
42 {this.state.error?.stack}
43 </pre>
44 </details>
45 </div>
46 )
47 );
48 }
49
50 return this.props.children;
51 }
52}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 />);