20. React use
20. React use

20. React use

Authors
Date
May 11, 2025
Tags
react
Published
Published
Slug
notion image

React에서 비동기 데이터를 다루는 다양한 방식

React는 state 중심의 UI 프레임워크다.
즉, 사용자 인터페이스에 표시되는 동적 데이터는 모두 state를 통해 관리되며, 이 state가 변경될 때마다 컴포넌트는 다시 렌더링된다.
그렇다면 이런 흐름 속에서 비동기 데이터는 어떻게 처리해야 할까?
예를 들어 서버에서 상품 목록을 가져온다거나, 외부 API에서 날씨 정보를 받아오는 등의 작업은 **비동기(Promise 기반)**이다.
이러한 데이터를 React에서 표현하기 위해 가장 기본적인 패턴은 다음과 같다:
 

useEffect를 활용한 비동기 데이터 로딩

React의 대표적인 비동기 데이터 처리 방법은 useEffect 훅을 사용하는 것이다.
'use client'; import { useEffect, useState } from 'react'; import { Product } from '@/libs/types/types'; import { fetchProducts } from '@/libs/fetchProducts'; export function Products() { const [products, setProducts] = useState<Product[]>([]); useEffect(() => { (async () => { const result = await fetchProducts(); setProducts(result); })(); }, []); return ( <ul className="flex flex-col gap-4"> {products.map((product) => ( <li key={product.id} className="border border-green-400"> <strong>{product.title}</strong> </li> ))} </ul> ); }

흐름 요약

  1. products라는 초기 상태값을 빈 배열로 설정
  1. useEffect 안에서 비동기 데이터를 호출
  1. setState로 데이터를 갱신
  1. 컴포넌트가 리렌더링되어 새로운 데이터가 화면에 표시됨
이 방식은 클라이언트에서 데이터를 가져오는 전통적인 패턴이며, CSR(Client Side Rendering)에서 주로 사용된다.
 

서버 사이드에서 비동기 데이터를 처리하려면?

Next.js나 Remix처럼 **서버 사이드 렌더링(SSR)**을 지원하는 프레임워크에서는
초기 렌더링 시점부터 데이터를 가져와 HTML을 완성해야 하는 요구가 있다.
하지만 서버에서는 useEffect나 useState를 사용할 수 없다.
(서버는 DOM이 없고, 리렌더링이라는 개념 자체가 존재하지 않기 때문)
그래서 등장한 방식이 바로 비동기 컴포넌트다.
 

비동기 컴포넌트와 Suspense

컴포넌트 자체를 비동기로 만들고, Suspense를 활용해 로딩 상태를 처리하는 패턴이다.
// 잘못된 사용 (클라이언트 컴포넌트에 async 사용) 'use client'; import { fetchProducts } from '@/libs/fetchProducts'; export async function Products() { const products = await fetchProducts(); return ( <ul> {products.map((product) => ( <li key={product.id}>{product.title}</li> ))} </ul> ); }
이 방식은 잘못된 사용이다. async 컴포넌트는 서버에서만 사용할 수 있으며, 클라이언트 컴포넌트에는 async를 붙일 수 없다.

올바른 구조

// 클라이언트 컴포넌트 'use client'; import { Product } from '@/libs/types/types'; export function Products({ products }: { products: Product[] }) { return ( <ul> {products.map((product) => ( <li key={product.id}>{product.title}</li> ))} </ul> ); }
// 서버 컴포넌트 import { fetchProducts } from '@/libs/fetchProducts'; import { Products } from './products'; export async function ProductsContainer() { const products = await fetchProducts(); return <Products products={products} />; }
이렇게 서버에서 데이터를 미리 받아서 클라이언트 컴포넌트에 props로 전달하면, 클라이언트는 useEffect 없이 데이터를 렌더링할 수 있다.
하지만 이런 방식은 비동기 데이터를 다루기 위해 컴포넌트 자체가 비동기 컴포넌트가 되어야하며, 이에 따라 컴포넌트 구조가 분리되어야 하는 불편함이 따르기도한다.
 

use 훅의 등장

React 19에서 use(promise)라는 새로운 훅이 실험적으로 도입되며 이 문제를 해결할 수 있게 되었다.

구조를 단순하게

// 서버 컴포넌트 import { fetchProducts } from '@/libs/fetchProducts'; import { Suspense } from 'react'; import { Products } from './products'; export default function Page() { const productsPromise = fetchProducts(); return ( <div> <h1>Use Example</h1> <Suspense fallback={<div>Loading...</div>}> <Products productsPromise={productsPromise} /> </Suspense> </div> ); }
// 클라이언트 컴포넌트 'use client'; import { Product } from '@/libs/types/types'; import { use } from 'react'; export function Products({ productsPromise }: { productsPromise: Promise<Product[]> }) { const products = use(productsPromise); return ( <ul> {products.map((product) => ( <li key={product.id}>{product.title}</li> ))} </ul> ); }
이제는 클라이언트 컴포넌트에서도 직접 비동기 데이터를 받아 사용할 수 있으며
useEffect도, 별도의 서버 컴포넌트도 필요 없다.
 

마무리

React는 원래 state 중심으로 동작하는 라이브러리이기 때문에, 비동기 데이터와 UI의 관계를 잘 정리해두는 것이 중요하다.
  • useEffect는 클라이언트에서의 비동기 데이터 패칭에 적합
  • 서버에서 데이터를 패칭할 땐 서버 컴포넌트를 분리하거나
  • 실험적인 use를 활용하면 더욱 깔끔한 구조를 만들 수 있다
react19에 추가된 use훅으로 비동기 데이터를 다루는 방식 또한 훨씬 자연스럽고 직관적으로 진화하고 있다.
이를 잘 활용하면 사용자 경험과 개발 효율성 모두를 향상시킬 수 있다.
 

예제

이곳에서 예제를 확인해보실 수 있습니다.
  1. client-component
  1. async-component
  1. use-component
순으로 비동기 데이터 처리 방식의 변화를 확인해보시는것을 추천드립니다.
 
react-use
entrolECUpdated Jun 29, 2025