use는 Promise나 context 같은 리소스의 값을 읽을 수 있게 해주는 React API예요.
const value = use(resource);
쉽게 말하면, 비동기 데이터나 context 값을 컴포넌트 안에서 직접 읽어올 수 있게 해주는 새로운 방법이에요!
use(resource)컴포넌트에서 use를 호출해서 Promise나 context 같은 리소스의 값을 읽을 수 있어요.
// 예시
import { use } from 'react';
function MessageComponent({ messagePromise }) {
const message = use(messagePromise);
const theme = use(ThemeContext);
// ...
React Hooks와 달리, use는 if 같은 조건문이나 반복문 안에서 호출할 수 있어요. React Hooks처럼, use를 호출하는 함수는 반드시 컴포넌트나 Hook이어야 해요.
Promise와 함께 호출될 때, use API는 Suspense와 Error Boundaries와 통합돼요. use를 호출하는 컴포넌트는 use에 전달된 Promise가 pending 상태인 동안 suspend(일시 중단)돼요. use를 호출하는 컴포넌트가 Suspense 경계로 감싸져 있으면, fallback이 표시될 거예요. Promise가 resolve되면, Suspense fallback은 use API가 반환한 데이터를 사용하는 렌더링된 컴포넌트로 대체돼요. use에 전달된 Promise가 reject되면, 가장 가까운 Error Boundary의 fallback이 표시돼요.
use API는 Promise의 resolve된 값이나 context 같이 리소스에서 읽어온 값을 반환해요.
use API는 반드시 컴포넌트나 Hook 안에서 호출해야 해요.use보다 async와 await를 사용하는 것이 좋아요. async와 await는 await가 호출된 지점부터 렌더링을 이어가지만, use는 데이터가 resolve된 후에 컴포넌트를 다시 렌더링해요.use로 context 읽기context가 use에 전달되면, useContext와 비슷하게 동작해요. useContext는 컴포넌트의 최상위 레벨에서 호출해야 하지만, use는 if 같은 조건문이나 for 같은 반복문 안에서 호출할 수 있어요. use는 더 유연하기 때문에 useContext보다 선호돼요.
// 예시
import { use } from 'react';
function Button() {
const theme = use(ThemeContext);
// ...
use는 전달한 context에 대한 context 값을 반환해요. context 값을 결정하기 위해 React는 컴포넌트 트리를 검색해서 해당 context에 대해 위에 있는 가장 가까운 context provider를 찾아요.
Button에 context를 전달하려면, 버튼이나 부모 컴포넌트 중 하나를 해당 context provider로 감싸세요.
// 예시
function MyPage() {
return (
<ThemeContext value="dark">
<Form />
</ThemeContext>
);
}
function Form() {
// ... 내부에서 버튼들을 렌더링해요 ...
}
provider와 Button 사이에 몇 개의 컴포넌트 레이어가 있는지는 상관없어요. Form 내부 어디에서든 Button이 use(ThemeContext)를 호출하면, "dark"를 값으로 받을 거예요.
useContext와 달리, use는 if 같은 조건문이나 반복문 안에서 호출할 수 있어요.
// 예시
function HorizontalRule({ show }) {
if (show) {
const theme = use(ThemeContext);
return <hr className={theme} />;
}
return false;
}
use가 if 문 안에서 호출되어서 Context에서 조건부로 값을 읽을 수 있어요.
⚠️ 주의
useContext처럼,use(context)는 항상 호출하는 컴포넌트 위에 있는 가장 가까운 context provider를 찾아요. 위쪽으로 검색하고use(context)를 호출하는 컴포넌트 안의 context providers는 고려하지 않아요.
// 예시
import { createContext, use } from 'react';
const ThemeContext = createContext(null);
export default function MyApp() {
return (
<ThemeContext value="dark">
<Form />
</ThemeContext>
)
}
function Form() {
return (
<Panel title="Welcome">
<Button show={true}>Sign up</Button>
<Button show={false}>Log in</Button>
</Panel>
);
}
function Panel({ title, children }) {
const theme = use(ThemeContext);
const className = 'panel-' + theme;
return (
<section className={className}>
<h1>{title}</h1>
{children}
</section>
)
}
function Button({ show, children }) {
if (show) {
const theme = use(ThemeContext);
const className = 'button-' + theme;
return (
<button className={className}>
{children}
</button>
);
}
return false
}
.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}
Server Component에서 Client Component로 Promise를 prop으로 전달해서 서버에서 클라이언트로 데이터를 스트리밍할 수 있어요.
// 예시
import { fetchMessage } from './lib.js';
import { Message } from './message.js';
export default function App() {
const messagePromise = fetchMessage();
return (
<Suspense fallback={<p>waiting for message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}
그러면 Client Component는 prop으로 받은 Promise를 use API에 전달해요. 이렇게 하면 Client Component가 원래 Server Component에서 생성된 Promise에서 값을 읽을 수 있어요.
// message.js
'use client';
import { use } from 'react';
export function Message({ messagePromise }) {
const messageContent = use(messagePromise);
return <p>Here is the message: {messageContent}</p>;
}
Message가 Suspense로 감싸져 있기 때문에, Promise가 resolve될 때까지 fallback이 표시될 거예요. Promise가 resolve되면, use API가 값을 읽고 Message 컴포넌트가 Suspense fallback을 대체할 거예요.
// src/message.js
"use client";
import { use, Suspense } from "react";
function Message({ messagePromise }) {
const messageContent = use(messagePromise);
return <p>Here is the message: {messageContent}</p>;
}
export function MessageContainer({ messagePromise }) {
return (
<Suspense fallback={<p>⌛Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}
// src/App.js
import { useState } from "react";
import { MessageContainer } from "./message.js";
function fetchMessage() {
return new Promise((resolve) => setTimeout(resolve, 1000, "⚛️"));
}
export default function App() {
const [messagePromise, setMessagePromise] = useState(null);
const [show, setShow] = useState(false);
function download() {
setMessagePromise(fetchMessage());
setShow(true);
}
if (show) {
return <MessageContainer messagePromise={messagePromise} />;
} else {
return <button onClick={download}>Download message</button>;
}
}
// src/index.js
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';
// TODO: update this example to use
// the Codesandbox Server Component
// demo environment once it is created
import App from './App';
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<App />
</StrictMode>
);
**심층 탐구: Server Component에서 Promise를 resolve해야 할까요, 아니면 Client Component에서?**참고
Server Component에서 Client Component로 Promise를 전달할 때, resolve된 값은 서버와 클라이언트 간에 전달되려면 직렬화 가능해야 해요. 함수 같은 데이터 타입은 직렬화할 수 없어서 그런 Promise의 resolve된 값이 될 수 없어요.
Promise는 Server Component에서 Client Component로 전달되고 use API로 Client Component에서 resolve될 수 있어요. Server Component에서 await로 Promise를 resolve하고 필요한 데이터를 Client Component에 prop으로 전달할 수도 있어요.
// 예시
export default async function App() {
const messageContent = await fetchMessage();
return <Message messageContent={messageContent} />
}
하지만 Server Component에서 await를 사용하면 await 문이 완료될 때까지 렌더링이 차단돼요. Server Component에서 Client Component로 Promise를 전달하면 Promise가 Server Component의 렌더링을 차단하는 것을 방지할 수 있어요.
어떤 경우에는 use에 전달된 Promise가 reject될 수 있어요. 거부된 Promise는 다음 방법으로 처리할 수 있어요:
⚠️ 주의
use는 try-catch 블록 안에서 호출할 수 없어요. try-catch 블록 대신 컴포넌트를 Error Boundary로 감싸거나, Promise의.catch메서드로 대체 값을 제공하세요.
Promise가 reject되었을 때 사용자에게 에러를 표시하고 싶다면, Error Boundary를 사용할 수 있어요. Error Boundary를 사용하려면, use API를 호출하는 컴포넌트를 Error Boundary로 감싸세요. use에 전달된 Promise가 reject되면 Error Boundary의 fallback이 표시될 거예요.
// src/message.js
"use client";
import { use, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
export function MessageContainer({ messagePromise }) {
return (
<ErrorBoundary fallback={<p>⚠️Something went wrong</p>}>
<Suspense fallback={<p>⌛Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
</ErrorBoundary>
);
}
function Message({ messagePromise }) {
const content = use(messagePromise);
return <p>Here is the message: {content}</p>;
}
// src/App.js
import { useState } from "react";
import { MessageContainer } from "./message.js";
function fetchMessage() {
return new Promise((resolve, reject) => setTimeout(reject, 1000));
}
export default function App() {
const [messagePromise, setMessagePromise] = useState(null);
const [show, setShow] = useState(false);
function download() {
setMessagePromise(fetchMessage());
setShow(true);
}
if (show) {
return <MessageContainer messagePromise={messagePromise} />;
} else {
return <button onClick={download}>Download message</button>;
}
}
// src/index.js
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';
// TODO: update this example to use
// the Codesandbox Server Component
// demo environment once it is created
import App from './App';
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<App />
</StrictMode>
);
// package.json
{
"dependencies": {
"react": "19.0.0",
"react-dom": "19.0.0",
"react-scripts": "^5.0.0",
"react-error-boundary": "4.0.3"
},
"main": "/index.js"
}
Promise.catch로 대체 값 제공하기use에 전달된 Promise가 reject되었을 때 대체 값을 제공하고 싶다면 Promise의 catch 메서드를 사용할 수 있어요.
// 예시
import { Message } from './message.js';
export default function App() {
const messagePromise = new Promise((resolve, reject) => {
reject();
}).catch(() => {
return "no new message found.";
});
return (
<Suspense fallback={<p>waiting for message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}
Promise의 catch 메서드를 사용하려면, Promise 객체에서 catch를 호출하세요. catch는 에러 메시지를 인자로 받는 함수 하나를 인자로 받아요. catch에 전달된 함수가 반환하는 것이 무엇이든 Promise의 resolve된 값으로 사용될 거예요.
use를 React 컴포넌트나 Hook 함수 바깥에서 호출하거나, try-catch 블록 안에서 use를 호출하고 있어요. try-catch 블록 안에서 use를 호출하고 있다면, 컴포넌트를 Error Boundary로 감싸거나, Promise의 catch를 호출해서 에러를 잡고 Promise를 다른 값으로 resolve하세요. 이 예시들을 참고하세요.
React 컴포넌트나 Hook 함수 바깥에서 use를 호출하고 있다면, use 호출을 React 컴포넌트나 Hook 함수로 옮기세요.
// 예시
function MessageComponent({messagePromise}) {
function download() {
// ❌ `use`를 호출하는 함수가 컴포넌트나 Hook이 아니에요
const message = use(messagePromise);
// ...
대신, 컴포넌트 클로저 바깥에서 use를 호출하세요. use를 호출하는 함수가 컴포넌트나 Hook이어야 해요.
// 예시
function MessageComponent({messagePromise}) {
// ✅ `use`가 컴포넌트에서 호출되고 있어요.
const message = use(messagePromise);
// ...