모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

12강 / 전체 30강

데이터 가져오기(fetch)

6분 읽기 조회 5

fetch와 async/await로 실제 API 데이터를 가져오고, 로딩·에러·성공 상태를 처리하는 커스텀 훅 useFetch를 직접 만들어봅니다.

🌐 외부 데이터 패칭의 3가지 상태

API에서 데이터를 가져올 때는 항상 세 가지 상태를 처리해야 합니다: 로딩 중 / 성공 / 실패. 이 세 상태를 올바르게 처리하는 것이 좋은 UX의 기본입니다.

실제 API(JSONPlaceholder)에서 사용자 목록을 가져오는 예제로 시작합니다:

interface User {
  id: number
  name: string
  email: string
  phone: string
}

function UserList() {
  const [users, setUsers] = useState<User[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    async function fetchUsers() {
      try {
        setLoading(true)
        const res = await fetch('https://jsonplaceholder.typicode.com/users')
        if (!res.ok) throw new Error(`HTTP ${res.status}`)
        const data: User[] = await res.json()
        setUsers(data)
      } catch (e) {
        setError(e instanceof Error ? e.message : '알 수 없는 오류')
      } finally {
        setLoading(false)
      }
    }

    fetchUsers()
  }, [])

  if (loading) return <div className="text-center p-8">불러오는 중...</div>
  if (error) return <div className="text-red-500 p-8">오류: {error}</div>

  return (
    <ul className="divide-y">
      {users.map((user) => (
        <li key={user.id} className="py-3">
          <p className="font-bold">{user.name}</p>
          <p className="text-sm text-gray-500">{user.email}</p>
        </li>
      ))}
    </ul>
  )
}

useEffect 안에서 async/await를 사용할 때는 이펙트 함수 자체를 async로 만들 수 없습니다. 내부에 async 함수를 선언하고 즉시 호출하는 패턴을 사용합니다.

♻️ 커스텀 훅 useFetch — 로직 재사용

위의 로딩/에러/데이터 패턴은 여러 컴포넌트에서 반복됩니다. 이를 커스텀 훅으로 추출하면 재사용할 수 있습니다. 커스텀 훅은 이름이 use로 시작하는 함수입니다.

// hooks/useFetch.ts
import { useState, useEffect } from 'react'

interface FetchState<T> {
  data: T | null
  loading: boolean
  error: string | null
}

export function useFetch<T>(url: string): FetchState<T> {
  const [state, setState] = useState<FetchState<T>>({
    data: null,
    loading: true,
    error: null,
  })

  useEffect(() => {
    let cancelled = false  // 컴포넌트 언마운트 시 상태 업데이트 방지

    async function fetchData() {
      setState((prev) => ({ ...prev, loading: true, error: null }))
      try {
        const res = await fetch(url)
        if (!res.ok) throw new Error(`HTTP ${res.status}`)
        const data: T = await res.json()
        if (!cancelled) setState({ data, loading: false, error: null })
      } catch (e) {
        if (!cancelled) setState({
          data: null,
          loading: false,
          error: e instanceof Error ? e.message : '오류 발생',
        })
      }
    }

    fetchData()
    return () => { cancelled = true }  // 클린업: 언마운트 시 응답 무시
  }, [url])

  return state
}

이제 어느 컴포넌트에서든 한 줄로 데이터를 가져올 수 있습니다:

function UserList() {
  const { data: users, loading, error } = useFetch<User[]>(
    'https://jsonplaceholder.typicode.com/users'
  )

  if (loading) return <div>로딩 중...</div>
  if (error) return <div>오류: {error}</div>
  if (!users) return null

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name} — {user.email}</li>
      ))}
    </ul>
  )
}

🔁 URL이 바뀌면 자동 재패치

function PostViewer() {
  const [selectedId, setSelectedId] = useState(1)
  const { data: post, loading } = useFetch<Post>(
    `https://jsonplaceholder.typicode.com/posts/${selectedId}`
  )

  return (
    <div>
      <div className="flex gap-2 mb-4">
        {[1, 2, 3, 4, 5].map((id) => (
          <button
            key={id}
            onClick={() => setSelectedId(id)}
            className={selectedId === id ? 'text-indigo-600 font-bold' : ''}
          >
            {id}번
          </button>
        ))}
      </div>
      {loading ? <div>로딩...</div> : <p>{post?.title}</p>}
    </div>
  )
}

버튼을 클릭하면 URL이 바뀌고, useFetch가 새 URL을 감지해 자동으로 재패치합니다. 이 패턴이 커스텀 훅의 핵심 가치입니다.

⚠️ fetch의 한계 — 나중에 TanStack Query로

직접 만든 useFetch는 학습 목적으로는 훌륭하지만 실무에서는 한계가 있습니다:

  • 캐싱이 없어 동일 URL을 여러 컴포넌트가 중복 요청합니다.
  • 재시도 로직이 없습니다.
  • 낙관적 업데이트(optimistic update)가 어렵습니다.

실무에서는 TanStack Query(React Query)를 사용합니다. 뒤 강의에서 다룹니다.

✅ 정리

  • API 패칭 시 항상 로딩/에러/성공 세 상태를 관리합니다.
  • useEffect 안 async: 이펙트 함수 내부에 async 함수 선언 후 호출.
  • 클린업에서 cancelled 플래그로 언마운트 후 상태 업데이트를 방지합니다.
  • useFetch 커스텀 훅으로 패칭 로직을 한 곳에 캡슐화해 재사용합니다.

관련 주제

  • fetch API
  • async await
  • useEffect 데이터 요청
  • 로딩 에러 상태 처리
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 리액트 입문 — 컴포넌트로 만드는 웹
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

리액트 입문 — 컴포넌트로 만드는 웹

이 강의가 속한 시리즈는 총 30강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...