본문 바로가기
Frontend2024년 10월 18일8분 읽기

React Suspense와 ErrorBoundary 조합 패턴

YS
김영삼
조회 399
React Suspense와 ErrorBoundary 조합 패턴

선언적 비동기 상태 관리

React의 Suspense는 비동기 작업의 로딩 상태를, ErrorBoundary는 에러 상태를 선언적으로 처리합니다. 두 컴포넌트를 조합하면 try-catch-finally와 유사한 패턴을 JSX로 표현할 수 있습니다.

ErrorBoundary 구현

import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }

  static getDerivedStateFromError(error) {
    return { error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('ErrorBoundary caught:', error, errorInfo);
    if (this.props.onError) {
      this.props.onError(error, errorInfo);
    }
  }

  reset = () => {
    this.setState({ error: null });
  };

  render() {
    if (this.state.error) {
      if (this.props.fallback) {
        return this.props.fallback({
          error: this.state.error,
          reset: this.reset
        });
      }
      return <div>Something went wrong.</div>;
    }
    return this.props.children;
  }
}

Suspense + ErrorBoundary 조합

function App() {
  return (
    <ErrorBoundary fallback={({ error, reset }) => (
      <div className="error-container">
        <h2>오류가 발생했습니다</h2>
        <p>{error.message}</p>
        <button onClick={reset}>다시 시도</button>
      </div>
    )}>
      <Suspense fallback={<LoadingSpinner />}>
        <UserProfile userId={1} />
      </Suspense>
    </ErrorBoundary>
  );
}

// 중첩 Suspense로 세밀한 로딩 제어
function Dashboard() {
  return (
    <ErrorBoundary fallback={ErrorFallback}>
      <Suspense fallback={<HeaderSkeleton />}>
        <Header />
      </Suspense>
      <div className="grid">
        <Suspense fallback={<ChartSkeleton />}>
          <SalesChart />
        </Suspense>
        <ErrorBoundary fallback={ErrorFallback}>
          <Suspense fallback={<TableSkeleton />}>
            <RecentOrders />
          </Suspense>
        </ErrorBoundary>
      </div>
    </ErrorBoundary>
  );
}

React Query와 Suspense 통합

import { useSuspenseQuery } from '@tanstack/react-query';

function UserProfile({ userId }) {
  const { data: user } = useSuspenseQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch('/api/users/' + userId).then(r => {
      if (!r.ok) throw new Error('사용자를 찾을 수 없습니다');
      return r.json();
    }),
  });

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

// 병렬 데이터 로딩
function UserDashboard({ userId }) {
  const { data: user } = useSuspenseQuery({
    queryKey: ['user', userId],
    queryFn: fetchUser,
  });
  const { data: posts } = useSuspenseQuery({
    queryKey: ['posts', userId],
    queryFn: () => fetchPosts(userId),
  });

  return <div>{user.name}의 게시글: {posts.length}개</div>;
}

AsyncBoundary 유틸리티 컴포넌트

function AsyncBoundary({ children, pendingFallback, rejectedFallback }) {
  return (
    <ErrorBoundary fallback={rejectedFallback}>
      <Suspense fallback={pendingFallback}>
        {children}
      </Suspense>
    </ErrorBoundary>
  );
}

// 사용
<AsyncBoundary
  pendingFallback={<Skeleton />}
  rejectedFallback={({ error, reset }) => (
    <ErrorCard error={error} onRetry={reset} />
  )}
>
  <DataComponent />
</AsyncBoundary>
  • ErrorBoundary는 클래스 컴포넌트로만 구현 가능하지만, 래퍼 패턴으로 함수 컴포넌트처럼 사용합니다
  • Suspense 경계를 세밀하게 나누면 부분적 로딩 UI를 제공할 수 있습니다
  • 이벤트 핸들러 내 에러는 ErrorBoundary에서 잡히지 않으므로 별도 처리가 필요합니다
  • React 19에서는 use() 훅으로 Promise를 직접 Suspense와 통합할 수 있습니다

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록