Ch.18 API 연동 — fetch와 데이터 흐름

React에서 API 호출하기

useEffect + fetch 패턴으로 컴포넌트 마운트 시 데이터를 로딩할 수 있다로딩/에러 상태를 관리할 수 있다받아온 데이터를 map()으로 리스트 렌더링할 수 있다

컴포넌트가 화면에 나타날 때 데이터를 불러오려면?

React 컴포넌트는 렌더링 → 화면 표시 → 이후 작업 순서로 동작합니다. API 호출은 '이후 작업'에서 해야 합니다.

렌더링과 데이터 로딩을 어떻게 동기화할까?

useEffect — 컴포넌트가 마운트된 후 실행되는 훅으로, API 호출의 최적 타이밍입니다.


article

핵심 내용

화면이 뜬 직후 데이터를 가져오는 useEffect + fetch 패턴

import { useState, useEffect } from "react";

function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    // 컴포넌트가 화면에 나타난 후 실행
    async function fetchUsers() {
      const res = await fetch("/api/users");
      const data = await res.json();
      setUsers(data);
    }
    fetchUsers();
  }, []); // [] = 마운트 시 1번만 실행

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

1. 렌더링: 빈 배열([])로 첫 화면 표시

2. useEffect: 마운트 후 fetch() 실행

3. 데이터 도착: setUsers()로 state 업데이트

4. 리렌더링: 새 데이터로 화면 갱신

의존성 배열 []의 의미 useEffect의 두 번째 인자 []는 "처음 마운트 시 1번만 실행"을 의미합니다. 빈 배열을 빼면 매 렌더마다 실행되어 무한 루프 위험!

데이터가 올 때까지 로딩 중을 보여줘야 합니다

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchUsers() {
      try {
        const res = await fetch("/api/users");
        if (!res.ok) throw new Error("서버 오류");
        const data = await res.json();
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }
    fetchUsers();
  }, []);

  // 조건부 렌더링
  if (loading) return <p>로딩 중...</p>;
  if (error) return <p>에러: {error}</p>;

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

finally 블록 성공이든 실패든 무조건 실행됩니다. setLoading(false)를 finally에 넣으면 로딩이 반드시 끝납니다.

배열 데이터를 map()으로 화면에 뿌립니다

// 기본 리스트 렌더링
function ProductList({ products }) {
  return (
    <div className="grid grid-cols-1 md:grid-cols-3
         gap-4 p-4">
      {products.map(product => (
        <div key={product.id}
          className="bg-white rounded-lg shadow
            p-4">
          <h3 className="font-bold">
            {product.name}
          </h3>
          <p className="text-gray-600">
            {product.price.toLocaleString()}원
          </p>
        </div>
      ))}
    </div>
  );
}
// 조건부 UI: 데이터가 비었을 때

function UserList({ users }) {
  if (users.length === 0) {
    return (
      <div className="text-center text-gray-500
           py-8">
        <p>등록된 사용자가 없습니다</p>
      </div>
    );
  }

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

key가 중요한 이유 React는 key를 보고 어떤 항목이 바뀌었는지 판단합니다. 고유한 id를 key로 쓰세요. 배열 index는 권장하지 않습니다.

API 호출에는 세 가지 상태가 항상 따라옵니다

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchUsers() {
      try {
        setLoading(true);
        const res = await fetch("/api/users");
        if (!res.ok) throw new Error("서버 오류");
        const data = await res.json();
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }
    fetchUsers();
  }, []);

  if (loading) return <p>로딩 중...</p>;
  if (error) return <p>에러: {error}</p>;
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

핵심 패턴: try → 성공 로직, catch → 에러 처리, finally → loading 해제. 이 3단 구조를 외워두세요.

컴포넌트 마운트 시 1번만 fetch를 실행하려면 useEffect의 두 번째 인자를?

map()에서 key 속성은 생략해도 동작에 문제가 없다.

로딩 상태를 확실히 끝내기 위해 setLoading(false)를 넣는 위치는?

key

핵심 용어

loading

true면 로딩 UI, false면 데이터/에러 표시

⚠️

error

null이면 정상, 문자열이면 에러 메시지 표시

📋

data

빈 배열 → fetch 성공 시 실제 데이터로 교체

Loading

데이터를 가져오는 중 — 스피너나 스켈레톤 UI 표시

Error

요청 실패 — 에러 메시지와 재시도 버튼 표시

Success

데이터 도착 — 실제 UI 렌더링

edit_note

정리 노트

React에서 API 호출하기

핵심 패턴

useEffect + fetch
컴포넌트 마운트 시 데이터를 자동으로 로딩
3대 상태
loading(로딩), error(에러), data(성공 데이터)
조건부 렌더링
if (loading) → 스피너, if (error) → 에러 메시지
map() 렌더링
배열 데이터를 map()으로 순회하여 리스트 표시

로딩/에러/데이터 — 이 3가지 상태만 잘 관리하면 API 앱의 기본은 완성입니다.

퀴즈와 인터랙션으로 더 깊이 학습하세요

play_circle인터랙티브 레슨 시작