React 18 베타에서 Suspense for Data Fetching 적용해보기
배경
작년부터 React 18 베타가 공개되면서 Suspense for Data Fetching을 실제 프로덕션에서 사용할 수 있는 가능성이 생겼다. 기존에는 로딩 상태를 각 컴포넌트에서 관리하다 보니 중첩된 컴포넌트 구조에서 로딩 UI가 깜빡이는 문제가 있었다.
기존 방식의 문제
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser(userId).then(data => {
setUser(data);
setLoading(false);
});
}, [userId]);
if (loading) return <Spinner />;
return <div>{user.name}</div>;
}
각 컴포넌트가 독립적으로 로딩 상태를 관리하면서 여러 개의 Spinner가 순차적으로 나타나는 현상이 발생했다.
Suspense 적용
const resource = fetchUserResource(userId);
function UserProfile() {
const user = resource.read(); // Suspend!
return <div>{user.name}</div>;
}
function App() {
return (
<Suspense fallback={<Spinner />}>
<UserProfile />
</Suspense>
);
}
Suspense 경계에서 하위 컴포넌트들의 로딩을 일괄 처리할 수 있게 되었다. 리소스 객체는 promise를 throw하는 방식으로 구현했다.
겪은 문제들
- 캐싱 전략: 단순 fetch wrapper로는 불필요한 재요청이 발생했다. 간단한 Map 기반 캐시를 구현해 해결했다.
- 에러 처리: Error Boundary와 함께 사용해야 하는데, 기존 에러 처리 로직과 통합하는 데 시간이 걸렸다.
- SSR 고려: Next.js에서는 아직 정식 지원 전이라 클라이언트 전용으로만 적용했다.
결론
아직 베타 단계지만 복잡한 로딩 상태 관리를 선언적으로 처리할 수 있다는 점에서 큰 장점이 있다. 정식 릴리스 후 단계적으로 전환할 계획이다.