React 19에서 새로운 use() 훅을 사용하여 더 깔끔한 Async 컴포넌트 만들기

발행: (2026년 3월 9일 AM 03:07 GMT+9)
8 분 소요
원문: Dev.to

Source: Dev.to

Teguh Coding

React를 오래 사용해 왔다면, 컴포넌트에서 비동기 데이터를 처리하는 번거로움을 겪어봤을 것입니다. useEffect, useState, 그리고 다양한 데이터‑fetching 라이브러리 사이에서 코드는 종종 복잡해집니다. React 19는 게임 체인저인 솔루션을 도입했습니다: use() 훅.

이 새로운 훅이 어떻게 비동기 데이터 처리를 단순화하고 코드를 훨씬 깔끔하게 만드는지 보여드리겠습니다.

옛 방식: 장황하고 오류가 발생하기 쉬움

React 19 이전에, 컴포넌트 내부에서 데이터를 가져오는 코드는 다음과 같은 형태였습니다:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, [userId]);

  if (loading) return ;
  if (error) return ;

  return {user.name};
}

간단한 데이터 가져오기에도 불구하고 많은 보일러플레이트가 필요합니다. 세 가지 상태를 관리하고, effect 의존성을 올바르게 처리하며, 어떤 엣지 케이스도 놓치지 않았는지 기대해야 합니다.

새로운 방법: 깔끔하고 선언적

React 19의 use() 훅은 모든 것을 바꿉니다. 프로미스가 해결되는 동안 컴포넌트를 일시 중단시켜, 로딩 및 오류 상태를 컴포넌트 트리의 더 높은 수준에서 처리합니다.

import { use } from 'react';

function UserProfile({ userId }) {
  const user = use(fetchUser(userId));

  return {user.name};
}

그게 전부입니다. 상태 관리도, 효과도, 보일러플레이트도 없습니다. 컴포넌트는 단순히 프로미스가 해결될 때까지 기다립니다.

실제 작동 방식

use() 훅은 프로미스를 받아서 다음과 같이 동작합니다:

  • 보류 중인 프로미스가 있으면 컴포넌트를 Suspended 상태로 만들고
  • 프로미스가 해결되면 재개하여 반환된 값을 사용합니다
  • 프로미스가 거부되면 오류를 발생시키며 (Error Boundary 로 잡을 수 있습니다)

다음은 현실적인 데이터‑fetching 시나리오를 포함한 전체 예시입니다:

import { use, Suspense, ErrorBoundary } from 'react';

// Async function that returns a promise
async function fetchUser(userId) {
  const res = await fetch(`/api/users/${userId}`);
  if (!res.ok) throw new Error('Failed to fetch user');
  return res.json();
}

function UserProfile({ userId }) {
  const user = use(fetchUser(userId));

  return (
    <>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <span>{user.role}</span>
    </>
  );
}

// Wrap it with Suspense and ErrorBoundary
function App() {
  return (
    <ErrorBoundary fallback={<div>Something went wrong.</div>}>
      <Suspense fallback={<div>Loading...</div>}>
        <UserProfile userId="123" />
      </Suspense>
    </ErrorBoundary>
  );
}

여러 비동기 값 처리하기

가장 뛰어난 기능 중 하나는 use()가 여러 프라미스를 우아하게 처리한다는 점입니다:

function Dashboard({ userId }) {
  const [user, posts] = use(Promise.all([
    fetchUser(userId),
    fetchPosts(userId)
  ]));

  return (
    <>
      <h2>Welcome, {user.name}</h2>
      {/* render posts here */}
    </>
  );
}

이제 Promise.all + useState + useEffect 조합을 사용할 필요 없이, 컴포넌트 안에서 비동기 코드를 바로 작성할 수 있습니다.

기존 패턴과 통합

use()를 기존 데이터‑패칭 라이브러리와 함께 계속 사용할 수 있습니다. 예를 들어 TanStack Query를 사용할 때:

import { useQuery } from '@tanstack/react-query';
import { use } from 'react';

function UserAvatar({ userId }) {
  const query = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId)
  });

  // Convert to promise for use()
  const user = use(
    query.isSuccess ? Promise.resolve(query.data) : new Promise(() => {})
  );

  return <img src={user.avatar} alt={user.name} />;
}

또는 간단한 래퍼를 만들 수 있습니다:

function useAsync(asyncFn) {
  return use(Promise.resolve().then(() => asyncFn()));
}

function UserCard({ userId }) {
  const user = useAsync(() => fetchUser(userId));
  return {user.name};
}

올바른 오류 처리

use() 훅은 React의 Error Boundary 패턴과 아름답게 통합됩니다:

function PostWithComments({ postId }) {
  const post = use(fetchPost(postId));
  const comments = use(fetchComments(postId));

  return (
    <>
      <h2>{post.title}</h2>
      <p>{post.content}</p>
      {/* render comments here */}
    </>
  );
}

// Any error in the component tree bubbles up to the Error Boundary
function ErrorFallback({ error }) {
  return <div>Something went wrong: {error.message}</div>;
}

function App() {
  return (
    <ErrorBoundary fallback={<ErrorFallback />}>
      <Suspense fallback={<div>Loading...</div>}>
        <PostWithComments postId="456" />
      </Suspense>
    </ErrorBoundary>
  );
}

use()useEffect를 언제 사용할까

use()를 사용할 때:

  • 컴포넌트에서 렌더링되는 데이터를 가져와야 할 때.
  • 더 간단하고 가독성이 높은 비동기 코드를 원할 때.
  • Suspense를 염두에 두고 구축할 때.

useEffect를 고수해야 할 때:

  • 부수 효과(로그 기록, 분석, 구독 등)를 처리할 때.
  • 데이터가 렌더링 결과에 직접적인 영향을 주지 않을 때.

왜 컴포넌트에서 렌더링할까?

  • when fetching이 발생하는 시점을 세밀하게 제어해야 합니다.

더 큰 그림

The use() hook은 단순한 문법 설탕이 아니라 React의 “React 19: The Era of Async.” 비전의 일부입니다.
Server Components와 Actions와 결합하면, 이는 React 애플리케이션을 구축하는 방식에 근본적인 변화를 나타냅니다.

각 컴포넌트마다 로딩 상태를 관리하는 대신, 최상위 레벨에서 한 번 정의하고 React가 나머지를 처리하도록 합니다.
이렇게 하면 더 깔끔한 사고 모델을 제공하고 코드 양도 줄어듭니다.

시도해 보기

React 19(또는 베타)를 사용하고 있다면, 컴포넌트에서 use()를 실험해 보세요. 보일러플레이트가 얼마나 빨리 사라지는지, 비동기 코드가 얼마나 더 읽기 쉬워지는지 놀라실 겁니다.

React의 미래는 async‑native입니다. 이를 받아들이세요.

새로운 use() 훅에 대해 어떻게 생각하시나요?
이미 사용해 보셨나요? 아래 댓글에 여러분의 생각을 남겨 주세요.

0 조회
Back to Blog

관련 글

더 보기 »