React <Suspense/> With Use Hook

김민기·2025년 8월 4일
0
// userFetcher.ts
let userPromise: Promise<User>;

export function fetchUser(): Promise<User> {
	if (!userPromise) {
		userPromise = fetch('/api/user').then(res => res.json());
	}
	return userPromise;
}
// UserInfo.tsx
import { use } from "react";
import { fetchUser } from "./userFetcher";

export default function UserInfo() {
	const user = use(fetchUser());
	return <div>안녕하세요, {user.name}</div>;
}
// App.tsx
import { Suspense } from "react";
import UserInfo from "./UserInfo";

export default function App() {
	return (
		<Suspense fallback={<div>Loading...</div>}>
			<UserInfo />
		</Suspense>
	);
}

전제조건 (Suspense + use)

  • use() 훅은 React 18 이상에서만 사용 가능 (18.3.0 이상)
  • use() 훅은 함수 컴포넌트의 최상단에서만 호출 가능 (*일반적인 훅 조건)
  • use() 는 동기적으로 실행되어야 함. await use(...), async function , .then(...) 모두 불가능
async function Component() {
  const user = use(fetchUser()); // Error
}

function Component() {
  const user = await use(fetchUser()); // Error
}

function Component() {
  const user = use(fetchUser().then(data => doSomething(data))); // Error
}
  • React 컴포넌트가 렌더링되는 동안 use() 가 바로 실행되어야 함.
  • Suspense 는 반드시 use() 보다 상위에 있어야 함.
    • React가 use() 가 던지는 Promise를 트리 위쪽에서 탐색하며 Suspense를 찾음
    • 따라서 use() 호출 전에 Suspense가 렌더링되고 있어야 함.
  • use() 에 넘기는 값은 같은 Promise 인스턴스여야 함
    • React는 use(promise) 로 받은 Promise를 한 번만 기다림
    • 이후 리렌더링 시에도 같은 Promise여야 Suspense가 실행하지 않음

단계별 흐름

📍1. 처음 렌더링

  • App 컴포넌트 렌더링 → UserInfo 컴포넌트 렌더링 시작
  • use(fetchUser()) 호출
  • userPromise는 아직 undefined 상태
  • fetchUser() 에서 fetch(...) 실행 → 새로운 Promise 생성
  • userPromise 에 생성된 Promise가 할당됨.
  • use() 는 아직 pending 상태인 Promise를 받음 → throw promise 실행
  • React는 이걸 감지해서 Suspense fallback(Loading…)을 보여줌

📍2. Promise 완료

  • fetch(...) 응답 도착 → Promise가 fullfilled 상태로 변경됨
  • React는 자동으로 UserInfo 를 다시 렌더링
  • 다시 use(fetchUser()) 호출
  • userPromise는 이미 존재하고 fullfilled 상태 → fetch가 다시 실행되지 않음
  • use() 는 resolve 값을 받아서 그대로 컴포넌트에서 사용
  • 화면에 “안녕하세요, ~~님” 같은 유저 정보가 표시됨.

📍3. 리렌더링 발생

부모 컴포넌트 상태 업데이트 등으로 리렌더링이 발생한다면

  • UserInfo() 다시 렌더링됨
  • use(fetchUser()) 다시 호출
  • userPromise 는 여전히 메모리에 존재하며 fullfilled 상태
  • fetch() 는 실행되지 않고, 캐시된 Promise의 결과를 그대로 사용
  • 다시 Suspense fallback 없이 유저 정보가 렌더링 됨.

마무리

React에서 Suspense를 사용할 때, use() 훅을 함께 사용하면 useEffect를 사용하지 않고 비동기 데이터를 간단하게 보여줄 수 있다고 생각합니다. 기존 useEffect 방식은 상태관리와, 로딩 처리, 조건 분기 등을 수동으로 관리해야 했다면, use를 사용하면 렌더링 도중 데이터를 기다리고, 준비되면 UI를 한 번에 렌더링하는 방식이기 때문에 화면 깜박임이 발생하지 않고 사용자에게 한 번에 완성된 UI를 보여줄 수 있다는 장점이 있습니다.

0개의 댓글