Ch.18 API 연동 — fetch와 데이터 흐름
React에서 API 호출하기
컴포넌트가 화면에 나타날 때 데이터를 불러오려면?
React 컴포넌트는 렌더링 → 화면 표시 → 이후 작업 순서로 동작합니다. API 호출은 '이후 작업'에서 해야 합니다.
렌더링과 데이터 로딩을 어떻게 동기화할까?
useEffect — 컴포넌트가 마운트된 후 실행되는 훅으로, API 호출의 최적 타이밍입니다.
핵심 내용
화면이 뜬 직후 데이터를 가져오는 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)를 넣는 위치는?
핵심 용어
loading
true면 로딩 UI, false면 데이터/에러 표시
error
null이면 정상, 문자열이면 에러 메시지 표시
data
빈 배열 → fetch 성공 시 실제 데이터로 교체
Loading
데이터를 가져오는 중 — 스피너나 스켈레톤 UI 표시
Error
요청 실패 — 에러 메시지와 재시도 버튼 표시
Success
데이터 도착 — 실제 UI 렌더링
정리 노트
React에서 API 호출하기
핵심 패턴
- useEffect + fetch
- 컴포넌트 마운트 시 데이터를 자동으로 로딩
- 3대 상태
- loading(로딩), error(에러), data(성공 데이터)
- 조건부 렌더링
- if (loading) → 스피너, if (error) → 에러 메시지
- map() 렌더링
- 배열 데이터를 map()으로 순회하여 리스트 표시
로딩/에러/데이터 — 이 3가지 상태만 잘 관리하면 API 앱의 기본은 완성입니다.
퀴즈와 인터랙션으로 더 깊이 학습하세요
play_circle인터랙티브 레슨 시작