React 애플리케이션에서 API와 통신하기 위한 비동기 데이터 요청은 필수적인 기능입니다. 이 문서에서는 JavaScript 내장 API인 fetch와 인기 있는 HTTP 클라이언트 라이브러리인 axios의 문법과 사용법을 비교하고 정리합니다.
Fetch는 모던 브라우저에 내장된 API로 별도의 설치가 필요 없습니다.
Axios는 외부 라이브러리이므로 설치가 필요합니다:
# npm 사용
npm install axios
# yarn 사용
yarn add axios
React 프로젝트에서 가져오기:
// Fetch는 별도의 import 필요 없음
// Axios import
import axios from 'axios';
기본적인 GET 요청:
fetch('https://api.example.com/data')
.then(response => {
// response.ok는 HTTP 상태 코드가 200-299 범위인 경우 true
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json(); // JSON으로 파싱
})
.then(data => {
console.log(data); // 데이터 사용
})
.catch(error => {
console.error('Fetch error:', error);
});
async/await 구문 사용:
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error('Fetch error:', error);
}
};
다양한 HTTP 메서드 사용:
// POST 요청
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'John Doe',
email: 'john@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
// PUT 요청
fetch('https://api.example.com/data/1', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Updated Name',
email: 'updated@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
// DELETE 요청
fetch('https://api.example.com/data/1', {
method: 'DELETE'
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
Fetch는 네트워크 오류만 reject하고, HTTP 오류 상태는 reject하지 않습니다:
fetch('https://api.example.com/nonexistent')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => console.log(data))
.catch(error => {
console.error('Error:', error);
// 여기서 네트워크 오류나 위에서 throw한 오류를 처리
});
헤더 설정:
fetch('https://api.example.com/data', {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN',
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
AbortController를 사용한 요청 취소:
const controller = new AbortController();
const signal = controller.signal;
// 요청 시작
fetch('https://api.example.com/data', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Fetch error:', error);
}
});
// 5초 후 요청 취소
setTimeout(() => {
controller.abort();
console.log('Fetch aborted after 5 seconds');
}, 5000);
useEffect와 함께 사용하는 패턴:
import { useState, useEffect } from 'react';
function DataFetchingComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch('https://api.example.com/data', { signal });
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const result = await response.json();
setData(result);
setError(null);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
setData(null);
}
} finally {
setLoading(false);
}
};
fetchData();
// 클린업 함수
return () => {
controller.abort();
};
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!data) return <div>No data</div>;
return (
<div>
<h1>Data:</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
기본적인 GET 요청:
axios.get('https://api.example.com/data')
.then(response => {
// 응답 데이터는 response.data에 있음
console.log(response.data);
})
.catch(error => {
console.error('Axios error:', error);
});
async/await 구문 사용:
const fetchData = async () => {
try {
const response = await axios.get('https://api.example.com/data');
console.log(response.data);
return response.data;
} catch (error) {
console.error('Axios error:', error);
}
};
다양한 HTTP 메서드 사용:
// POST 요청
axios.post('https://api.example.com/data', {
name: 'John Doe',
email: 'john@example.com'
})
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error));
// PUT 요청
axios.put('https://api.example.com/data/1', {
name: 'Updated Name',
email: 'updated@example.com'
})
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error));
// DELETE 요청
axios.delete('https://api.example.com/data/1')
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error));
// 여러 메서드를 method 옵션을 통해 사용
axios({
method: 'post',
url: 'https://api.example.com/data',
data: {
name: 'John Doe',
email: 'john@example.com'
}
})
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error));
Axios는 HTTP 오류 상태를 자동으로 reject합니다:
axios.get('https://api.example.com/nonexistent')
.then(response => {
console.log(response.data);
})
.catch(error => {
if (error.response) {
// 서버가 2xx 범위를 벗어난 상태 코드로 응답
console.error('Error status:', error.response.status);
console.error('Error data:', error.response.data);
} else if (error.request) {
// 요청이 이루어졌지만 응답을 받지 못함
console.error('Error request:', error.request);
} else {
// 오류를 발생시킨 요청을 설정하는 중에 문제 발생
console.error('Error message:', error.message);
}
console.error('Error config:', error.config);
});
헤더 설정:
// 요청별 헤더 설정
axios.get('https://api.example.com/data', {
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Accept': 'application/json'
}
})
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error));
// 전역 기본 헤더 설정
axios.defaults.headers.common['Authorization'] = 'Bearer YOUR_TOKEN';
axios.defaults.headers.post['Content-Type'] = 'application/json';
CancelToken을 사용한 요청 취소 (v0.22.0 이하):
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
// 요청 시작
axios.get('https://api.example.com/data', {
cancelToken: source.token
})
.then(response => console.log(response.data))
.catch(error => {
if (axios.isCancel(error)) {
console.log('Request canceled:', error.message);
} else {
console.error('Error:', error);
}
});
// 5초 후 요청 취소
setTimeout(() => {
source.cancel('Operation canceled by the user.');
}, 5000);
AbortController를 사용한 요청 취소 (v0.22.0 이상):
const controller = new AbortController();
// 요청 시작
axios.get('https://api.example.com/data', {
signal: controller.signal
})
.then(response => console.log(response.data))
.catch(error => {
if (error.name === 'CanceledError') {
console.log('Request canceled');
} else {
console.error('Error:', error);
}
});
// 5초 후 요청 취소
setTimeout(() => {
controller.abort();
}, 5000);
요청 및 응답 인터셉터:
// 요청 인터셉터 추가
axios.interceptors.request.use(
config => {
// 요청 보내기 전에 수행할 작업
console.log('Request sent:', config.url);
// 인증 토큰 추가
config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
return config;
},
error => {
// 요청 오류 처리
return Promise.reject(error);
}
);
// 응답 인터셉터 추가
axios.interceptors.response.use(
response => {
// 응답 데이터를 처리
console.log('Response received:', response.status);
return response;
},
error => {
// 응답 오류 처리
if (error.response && error.response.status === 401) {
// 인증 오류 처리 (예: 로그아웃 또는 토큰 갱신)
console.log('Authentication error');
}
return Promise.reject(error);
}
);
사용자 정의 설정으로 인스턴스 생성:
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
// 인스턴스 사용
api.get('/data')
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error));
useEffect와 함께 사용하는 패턴:
import { useState, useEffect } from 'react';
import axios from 'axios';
function DataFetchingComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
setLoading(true);
const response = await axios.get('https://api.example.com/data', {
signal: controller.signal
});
setData(response.data);
setError(null);
} catch (err) {
if (err.name !== 'CanceledError') {
setError(err.message);
setData(null);
}
} finally {
setLoading(false);
}
};
fetchData();
// 클린업 함수
return () => {
controller.abort();
};
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!data) return <div>No data</div>;
return (
<div>
<h1>Data:</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
| 기능 | Fetch | Axios |
|---|---|---|
| 브라우저 지원 | 최신 브라우저 (IE 불가) | 모든 브라우저 |
| 설치 필요 | 아니요 (내장 API) | 예 (외부 라이브러리) |
| JSON 자동 변환 | 수동 파싱 필요 | 자동 변환 |
| 요청 취소 | AbortController 사용 | CancelToken 또는 AbortController 사용 |
| 타임아웃 설정 | 직접 구현 필요 | 내장 지원 |
| 인터셉터 | 지원 안 함 | 지원 |
| 에러 처리 | HTTP 오류를 reject하지 않음 | HTTP 오류를 자동으로 reject |
| 업로드 진행 모니터링 | 지원 제한적 | 내장 지원 |
| CSRF 보호 | 수동 구현 필요 | 일부 내장 지원 |
| 응답 타임아웃 | 수동 구현 필요 | 내장 지원 |
Fetch 사용:
import { useState } from 'react';
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const response = await fetch('https://api.example.com/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || '로그인에 실패했습니다.');
}
const data = await response.json();
// 로그인 성공 처리
localStorage.setItem('token', data.token);
window.location.href = '/dashboard';
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
{error && <div className="error">{error}</div>}
<div>
<label>이메일:</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<label>비밀번호:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit" disabled={loading}>
{loading ? '로그인 중...' : '로그인'}
</button>
</form>
);
}
Axios 사용:
import { useState } from 'react';
import axios from 'axios';
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const response = await axios.post('https://api.example.com/login', {
email,
password
});
// 로그인 성공 처리 (Axios에서 data 프로퍼티에 바로 접근)
localStorage.setItem('token', response.data.token);
window.location.href = '/dashboard';
} catch (err) {
setError(
err.response?.data?.message ||
'로그인에 실패했습니다.'
);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
{error && <div className="error">{error}</div>}
<div>
<label>이메일:</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<label>비밀번호:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit" disabled={loading}>
{loading ? '로그인 중...' : '로그인'}
</button>
</form>
);
}
Fetch 사용 커스텀 훅:
import { useState, useEffect, useCallback } from 'react';
export function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchData = useCallback(async (customOptions = {}) => {
const controller = new AbortController();
const signal = controller.signal;
setLoading(true);
try {
const response = await fetch(url, {
...options,
...customOptions,
signal,
headers: {
'Content-Type': 'application/json',
...options.headers,
...customOptions.headers,
},
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP error ${response.status}`);
}
const result = await response.json();
setData(result);
setError(null);
return result;
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
throw err;
}
} finally {
setLoading(false);
}
return () => controller.abort();
}, [url, options]);
useEffect(() => {
if (!options.manual) {
const controller = new AbortController();
const signal = controller.signal;
setLoading(true);
fetch(url, { ...options, signal })
.then(response => {
if (!response.ok) {
return response.json().catch(() => ({}))
.then(errorData => {
throw new Error(errorData.message || `HTTP error ${response.status}`);
});
}
return response.json();
})
.then(data => {
setData(data);
setError(null);
})
.catch(err => {
if (err.name !== 'AbortError') {
setError(err.message);
}
})
.finally(() => {
setLoading(false);
});
return () => controller.abort();
}
}, [url, options]);
return { data, loading, error, fetchData };
}
Axios 사용 커스텀 훅:
import { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
export function useAxios(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const source = axios.CancelToken.source();
const fetchData = useCallback(async (customOptions = {}) => {
const controller = new AbortController();
setLoading(true);
try {
const response = await axios({
url,
cancelToken: source.token,
signal: controller.signal,
...options,
...customOptions,
headers: {
'Content-Type': 'application/json',
...options.headers,
...customOptions.headers,
},
});
setData(response.data);
setError(null);
return response.data;
} catch (err) {
if (!axios.isCancel(err)) {
setError(err.response?.data?.message || err.message);
throw err;
}
} finally {
setLoading(false);
}
return () => {
source.cancel('Operation canceled by cleanup.');
controller.abort();
};
}, [url, options, source]);
useEffect(() => {
if (!options.manual) {
setLoading(true);
const controller = new AbortController();
axios({
url,
cancelToken: source.token,
signal: controller.signal,
...options,
})
.then(response => {
setData(response.data);
setError(null);
})
.catch(err => {
if (!axios.isCancel(err)) {
setError(err.response?.data?.message || err.message);
}
})
.finally(() => {
setLoading(false);
});
return () => {
source.cancel('Operation canceled by cleanup.');
controller.abort();
};
}
return () => {
source.cancel('Operation canceled by cleanup.');
};
}, [url, options, source]);
return { data, loading, error, fetchData };
}
fetch와 axios 모두 React 애플리케이션에서 비동기 HTTP 요청을 처리하는 훌륭한 도구입니다.
프로젝트 요구사항, 팀의 선호도, 필요한 기능에 따라 적절한 도구를 선택하시기 바랍니다. 작은 프로젝트나 빠른 프로토타이핑에는 fetch가 충분할 수 있으며, 복잡한 프로젝트나 엔터프라이즈 애플리케이션에는 axios의 추가 기능과 확장성이 도움이 될 수 있습니다.