// 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>
);
}
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
}
use()
가 바로 실행되어야 함.Suspense
는 반드시 use()
보다 상위에 있어야 함.use()
가 던지는 Promise를 트리 위쪽에서 탐색하며 Suspense를 찾음use()
호출 전에 Suspense가 렌더링되고 있어야 함.use()
에 넘기는 값은 같은 Promise 인스턴스여야 함use(promise)
로 받은 Promise를 한 번만 기다림App
컴포넌트 렌더링 → UserInfo
컴포넌트 렌더링 시작use(fetchUser())
호출userPromise
는 아직 undefined 상태fetchUser()
에서 fetch(...)
실행 → 새로운 Promise 생성userPromise
에 생성된 Promise가 할당됨.use()
는 아직 pending 상태인 Promise를 받음 → throw promise
실행fetch(...)
응답 도착 → Promise가 fullfilled
상태로 변경됨UserInfo
를 다시 렌더링use(fetchUser())
호출userPromise
는 이미 존재하고 fullfilled
상태 → fetch가 다시 실행되지 않음use()
는 resolve 값을 받아서 그대로 컴포넌트에서 사용부모 컴포넌트 상태 업데이트 등으로 리렌더링이 발생한다면
UserInfo()
다시 렌더링됨use(fetchUser())
다시 호출userPromise
는 여전히 메모리에 존재하며 fullfilled
상태fetch()
는 실행되지 않고, 캐시된 Promise의 결과를 그대로 사용React에서 Suspense를 사용할 때, use() 훅을 함께 사용하면 useEffect를 사용하지 않고 비동기 데이터를 간단하게 보여줄 수 있다고 생각합니다. 기존 useEffect 방식은 상태관리와, 로딩 처리, 조건 분기 등을 수동으로 관리해야 했다면, use를 사용하면 렌더링 도중 데이터를 기다리고, 준비되면 UI를 한 번에 렌더링하는 방식이기 때문에 화면 깜박임이 발생하지 않고 사용자에게 한 번에 완성된 UI를 보여줄 수 있다는 장점이 있습니다.