use()는 렌더 중에 Promise나 Context 같은 리소스의 값을 읽는 React 19 API(Application Programming Interface)다. Promise가 아직 pending이면 컴포넌트가 suspend되어 가장 가까운 Suspense의 fallback이 보이고, 끝나면 그 값으로 다시 렌더된다. reject되면 가장 가까운 에러 경계(Error Boundary)로 던져진다.
// 서버 컴포넌트: 요청을 시작만 하고 await하지 않는다
async function Page() {
const commentsPromise = fetchComments()
return (
<Suspense fallback={<p>댓글 불러오는 중…</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
)
}
// 클라이언트 컴포넌트
'use client'
function Comments({ commentsPromise }) {
const comments = use(commentsPromise)
return <ul>{comments.map(c => <li key={c.id}>{c.text}</li>)}</ul>
}- 왜 await하지 않고 넘기나: 서버가 느린 데이터까지 기다리면 화면 전체가 늦는다. 셸과 fallback을 먼저 보내고, 데이터는 준비되는 대로 스트림으로 흘린다(스트리밍 SSR). 넘어간 Promise는 페이로드에서 참조로 직렬화된다
- 훅 규칙의 예외: 조건문·반복문 안에서도 호출할 수 있다(React의 규칙과 렌더 순수성).
use(SomeContext)도useContext와 달리 조건부로 부를 수 있다 - 함정: 클라이언트 컴포넌트 렌더 안에서
use(fetchComments())처럼 Promise를 새로 만들면, 렌더마다 새 Promise가 생겨 계속 suspend되거나 매번 다시 요청한다. Promise는 렌더 밖(서버 컴포넌트, 캐시하는 라이브러리)에서 와야 한다. 렌더 중에 읽는 것은 괜찮지만, 비동기 작업을 렌더 중에 새로 시작하는 것이 위험하다
서버는 await로, 클라이언트는 use()로 읽는다. 쓰기는 서버 함수와 서버 액션가 맡는다. Promise 자체는 Promise.