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하는 방식으로 구현했다.

겪은 문제들

  1. 캐싱 전략: 단순 fetch wrapper로는 불필요한 재요청이 발생했다. 간단한 Map 기반 캐시를 구현해 해결했다.
  2. 에러 처리: Error Boundary와 함께 사용해야 하는데, 기존 에러 처리 로직과 통합하는 데 시간이 걸렸다.
  3. SSR 고려: Next.js에서는 아직 정식 지원 전이라 클라이언트 전용으로만 적용했다.

결론

아직 베타 단계지만 복잡한 로딩 상태 관리를 선언적으로 처리할 수 있다는 점에서 큰 장점이 있다. 정식 릴리스 후 단계적으로 전환할 계획이다.

React 18 베타에서 Suspense for Data Fetching 적용해보기