React 18 Suspense와 데이터 페칭 패턴 정리

배경

올해 3월 React 18이 정식 출시되면서 Suspense for Data Fetching이 Concurrent Feature로 포함되었다. 기존 프로젝트에 점진적으로 적용하면서 몇 가지 패턴을 정리할 필요가 있었다.

기존 방식의 문제

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>;
}

매번 loading 상태를 관리하고, 에러 처리를 각 컴포넌트에서 반복해야 했다.

Suspense 적용

function UserProfile({ userId }) {
  const user = use(fetchUser(userId)); // experimental
  return <div>{user.name}</div>;
}

// 상위 컴포넌트
function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <ErrorBoundary>
        <UserProfile userId={123} />
      </ErrorBoundary>
    </Suspense>
  );
}

로딩과 에러 상태를 상위에서 선언적으로 처리할 수 있게 되었다.

주의사항

아직 use hook은 experimental이라 프로덕션에서는 React Query나 SWR 같은 라이브러리의 Suspense 모드를 사용하는 게 안전하다. React Query v4에서 suspense: true 옵션으로 간단히 적용 가능했다.

const { data } = useQuery(['user', userId], () => fetchUser(userId), {
  suspense: true
});

결론

Suspense는 컴포넌트 코드를 단순하게 만들어줬지만, 아직 생태계가 완전히 준비되지 않았다. 당분간은 검증된 라이브러리와 함께 사용하는 게 현실적이다.