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는 렌더 밖(서버 컴포넌트, 캐시하는 라이브러리)에서 와야 한다. 렌더 중에 읽는 것은 괜찮지만, 비동기 작업을 렌더 중에 새로 시작하는 것이 위험하다 -
같은 Promise를 여러 곳에 넘기기: Promise 하나를
<A promise={p} />,<B promise={p} />처럼 여러 컴포넌트에 넘겨도 요청은 한 번이다.fetchData()를 부른 순간 작업이 시작되고, 소비자들은 그 결과를 같이 기다릴 뿐이다. 이것은 React 기능이 아니라 Promise 자체의 성질이다. 반대로 각 컴포넌트에서fetchData()를 따로 부르면 Promise도 요청도 여러 개가 된다 -
공유 범위: 이 공유는 그 Promise를 만든 한 번의 렌더 안에서만 통한다. 다른 요청이나 다른 서버 인스턴스와는 나누지 않는다. 요청 하나 안의 여러 호출을 묶으려면 React.cache, 요청을 넘어 결과를 재사용하려면 "use cache"와 Cache Components를 쓴다
-
Suspense 경계 위치: fallback을 보여 주는 것은 suspend한 컴포넌트보다 위에 있는 가장 가까운 경계다. 그래서 서버 컴포넌트가
await한다면 경계는 그 컴포넌트 바깥에 있어야 한다.Page가 먼저await한 뒤<Suspense>를 반환하면, 그 경계는 아직 렌더되지 않았으므로 이 대기를 감싸지 못한다. 기다림을 감싸려면await를 경계 안쪽 자식 컴포넌트로 내리거나, 위 예처럼 Promise만 넘긴다
서버는 await로, 클라이언트는 use()로 읽는다. 쓰기는 서버 함수와 서버 액션가 맡는다. Promise 자체는 Promise.
출처: React - use