13장: 데이터 가져오기 – 비동기 코드와 API
리액트와 데이터 패칭의 모든 것: 실전 비동기 연동 가이드
리액트 앱이 실질적인 가치를 가지려면, 외부에서 데이터를 받아와 동적으로 화면을 구성할 수 있어야 합니다. 단순한 상태 관리에서 출발한 리액트는 이제 서버와의 비동기 통신을 빼놓고는 설명할 수 없습니다. 이 장에서는 실제 API와 소통하는 과정을 완전히 뜯어보고, 실무에서 반드시 맞닥뜨리는 비동기 데이터 패칭의 원리와 실전 코드를 압축해 전합니다.
비동기 통신의 원칙과 리액트에서의 위치
웹 앱에서 "데이터를 가져온다"는 것은 곧 서버에 요청을 보내고, 응답을 받아서 화면에 반영하는 일입니다. 이 과정은 즉시 끝나지 않고, 언제 결과가 도착할지 알 수 없기 때문에 '비동기(asynchronous)'로 처리합니다. 리액트에서는 상태(state)를 활용해 데이터의 변화를 감지하고, 비동기 로직을 조율하여 자연스럽게 UI를 최신 상태로 유지합니다.
가장 기본적인 방법: useEffect와 fetch 함수
리액트 함수형 컴포넌트에서 데이터 패칭의 정석은 useEffect 훅을 활용하는 것입니다. useEffect는 "컴포넌트가 화면에 나타났을 때, 또는 의존 값이 변경될 때 실행할 작업"을 지정합니다. 내부에서 fetch 함수를 사용하면 자바스크립트 내장 Promise 기반 API로 간단히 GET 요청을 날릴 수 있습니다.
import { useEffect, useState } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => {
if (!response.ok) throw new Error('데이터 불러오기 실패');
return response.json();
})
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(e => {
setError(e);
setLoading(false);
});
}, []);
if (loading) return <p>로딩 중입니다...</p>;
if (error) return <p>오류 발생: {error.message}</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
이 예제는 리액트에서 가장 단순하고 직관적으로 데이터를 패칭하는 공식입니다. useEffect는 빈 배열을 의존성으로 받아 최초 한 번만 실행되고, fetch로 데이터를 받아오며, 결과값은 useState로 관리합니다. 에러와 로딩 상태까지 함께 다루는 것이 실전에서 매우 중요합니다.
async/await, 그리고 axios로 더 우아하게
then/catch 체인 대신 async/await 문법을 쓰면 가독성이 크게 향상됩니다. 또한, axios 같은 HTTP 클라이언트를 활용하면 응답 및 에러 처리가 더 쉬워지고, 코드가 간결해집니다.
import { useEffect, useState } from 'react';
import axios from 'axios';
function PostList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
setPosts(res.data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return <p>로딩 중...</p>;
if (error) return <p>에러: {error.message}</p>;
return (
<ol>
{posts.slice(0,10).map(post => (
<li key={post.id}>{post.title}</li>
))}
</ol>
);
}
axios는 fetch와 달리 요청/응답 객체가 더 직관적이고, 자동으로 JSON 변환, 헤더 설정, 다양한 HTTP 메서드 지원 등을 제공해 실제 프로젝트에서 더욱 많이 사용됩니다.
실전에서 중요한 추가 팁
로딩 및 에러 관리: 사용자 경험을 위해 언제나 로딩 상태와 오류 상황을 명확히 처리해야 합니다.
의존성 배열 활용: useEffect의 두 번째 인자를 적절히 지정해 데이터 갱신 타이밍을 관리합니다.
정리(cleanup) 로직: 데이터 패칭 중 컴포넌트가 언마운트 된다면, 필요에 따라 AbortController로 요청 취소를 구현할 수 있습니다.
라이브러리의 힘: 비동기 상태 관리를 획기적으로 단순화하는 React Query(TanStack Query)도 익혀 두면 좋습니다. 캐싱, 자동 리패칭 등 실무에서 유용한 기능을 별도 코드 없이 누릴 수 있습니다.
마치며
데이터는 앱의 생명줄입니다. 리액트로 실전형 앱을 만든다면, 반드시 useEffect, fetch, axios, 그리고 최신 비동기 데이터 관리 라이브러리에 익숙해지는 것이 필요합니다. 다음 단계로 넘어가기 전, 지금까지 나온 예제를 손으로 직접 구현해보는 것이 실력을 키우는 가장 빠른 길입니다.