누구나 로고
Online CoursesFor BusinessLibraryTrend InsightsFree coursesNoticesContactCareers
모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.
로그인무료 회원가입
강좌리액트 입문 — 컴포넌트로 만드는 웹12강

12강 / 전체 28강

데이터 가져오기(fetch)

6분 읽기 조회 0

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
  • 누구나
이전 11강useEffect 기초다음 13강 상태 끌어올리기

댓글

0/1000

불러오는 중...

목차

  1. 1.리액트란 무엇인가
  2. 2.개발 환경 만들기 — Vite
  3. 3.JSX 기초
  4. 4.컴포넌트 만들기
  5. 5.Props로 데이터 전달
  6. 6.State와 useState
  7. 7.이벤트 처리
  8. 8.조건부 렌더링
  9. 9.리스트 렌더링과 key
  10. 10.폼 다루기
  11. 11.useEffect 기초
  12. 12.데이터 가져오기(fetch)
  13. 13.상태 끌어올리기
  14. 14.컴포넌트 합성과 재사용
  15. 15.useRef와 DOM 접근
  16. 16.커스텀 훅 만들기
  17. 17.Context API
  18. 18.useReducer
  19. 19.리스트 앱 실전 ①
  20. 20.리스트 앱 실전 ②
  21. 21.라우팅 — React Router
  22. 22.스타일링 방법들
  23. 23.리스트·상세 페이지 연결
  24. 24.비동기 상태 관리 패턴
  25. 25.성능 기초 — memo
  26. 26.에러 처리와 경계
  27. 27.환경변수와 빌드
  28. 28.배포하기

Support

  • Notices
  • FAQ
  • Contact Us
  • Community

Help & Info

  • Terms of Service
  • Privacy Policy
  • Refund Policy

Services

  • About Us
  • Sign up
  • New courses
  • Free courses
누구나 로고
Terms of ServicePrivacy PolicyRefund Policy

상호명: NUGUNA  |  대표자: 정우진  |  사업자등록번호: 392-32-01817  |  통신판매업신고: 제 2026-서울양천-0564 호

주소: 서울특별시 양천구 목동서로 100  |  이메일: nugunapass@gmail.com  |  전화: 010-6395-3043

© 2026 NUGUNA. All rights reserved.

KB예금주인증관리자
HomeCoursesLibraryContactMY