선언적 비동기 상태 관리
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