
import React, { useState, useEffect } from 'react';
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
function FetchExample() {
const [post, setPost] = useState<Post | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
setLoading(true);
setError(null);
fetch('https://jsonplaceholder.typicode.com/posts/1') // API 엔드포인트
.then(response => {
// fetch는 네트워크 오류가 아닌 이상 (예: 404, 500) 에러를 throw하지 않음
// 따라서 응답 상태(ok 또는 status)를 직접 확인해야 함
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 응답 본문을 JSON으로 파싱 (이 과정도 Promise 반환)
return response.json();
})
.then(data => {
setPost(data);
})
.catch(err => {
console.error("Fetch error:", err);
setError(err);
})
.finally(() => {
setLoading(false);
});
}, []); // 마운트 시 한 번만 실행
if (loading) return <div>로딩 중...</div>;
if (error) return <div>오류 발생: {error.message}</div>;
if (!post) return null;
return (
<div>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
);
}fetch('/api/users', {
method: 'POST', // HTTP 메서드
headers: {
'Content-Type': 'application/json', // 요청 본문 타입
// 'Authorization': 'Bearer YOUR_TOKEN', // 인증 토큰 등
},
body: JSON.stringify({ name: 'Alice', age: 30 }), // JavaScript 객체를 JSON 문자열로 변환
})
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error));response.ok や response.status を自分で確認する処理が必須です。response.json() や response.text() などを別途呼び出す必要があり、この処理もPromiseを返します。npm install axios
# 또는
yarn add axiosimport React, { useState, useEffect } from 'react';
import axios from 'axios'; // axios 임포트
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
function AxiosExample() {
const [post, setPost] = useState<Post | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
setLoading(true);
setError(null);
axios.get<Post>('https://jsonplaceholder.typicode.com/posts/1') // GET 요청 및 응답 타입 지정
.then(response => {
// 응답 데이터는 response.data 안에 자동으로 파싱되어 있음
setPost(response.data);
})
.catch(err => {
// Axios는 4xx, 5xx 응답 상태 코드일 경우 에러(rejected Promise)로 처리함
console.error("Axios error:", err);
setError(err);
})
.finally(() => {
setLoading(false);
});
}, []);
if (loading) return <div>로딩 중...</div>;
// Axios 에러 객체에서 구체적인 정보 확인 가능 (err.response, err.request 등)
if (error) return <div>오류 발생: {axios.isAxiosError(error) ? error.message : '알 수 없는 오류'}</div>;
if (!post) return null;
return (
<div>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
);
}axios.post('/api/users', { name: 'Bob', age: 25 }) // 두 번째 인자로 데이터 객체 전달
.then(response => console.log('Success:', response.data))
.catch(error => console.error('Error:', error.response?.data || error.message)); // 에러 응답 확인catch ブロックでエラーを処理できます。| 特徴 | Fetch API | Axios |
|---|---|---|
インストール | 不要(ブラウザ組み込み) | 必要(ライブラリ) |
Promiseベース | はい | はい |
JSON変換 | 手動(.json() の呼び出しが必要) | 自動 |
HTTPエラーの処理 | 手動(response.ok の確認が必要) | 自動(Promiseをreject) |
リクエストのキャンセル | AbortControllerを使用 | AbortControllerまたはCancelTokenを使用 |
インターセプター | なし(自前で実装) | あり(非常に便利) |
タイムアウト | なし(自前で実装) | あり(設定が簡単) |
互換性 | 主に最新ブラウザ | 古いブラウザにも比較的対応 |
バンドルサイズ | 影響なし | ライブラリ分だけ増加 |
AbortController のインスタンスを生成します。signal プロパティをfetchやAxiosのリクエストオプションに渡します。abort() メソッドを呼び出します。import React, { useState, useEffect } from 'react';
import axios from 'axios'; // Axios 사용 예시
function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!query) { // 검색어가 없으면 요청 안 함
setResults([]);
return;
}
// 1. AbortController 생성
const controller = new AbortController();
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.get(`/api/search?q=${query}`, {
// 2. signal 전달
signal: controller.signal,
});
setResults(response.data);
} catch (error) {
// 요청 취소 시 Axios는 'CanceledError' 발생시킴
if (axios.isCancel(error)) {
console.log('Request canceled:', error.message);
} else {
console.error('Search error:', error);
}
} finally {
setLoading(false);
}
};
fetchData();
// cleanup 함수: 컴포넌트 언마운트 또는 query 변경 시 이전 요청 취소
return () => {
// 3. abort() 호출하여 요청 취소
console.log(`Canceling request for query: ${query}`);
controller.abort();
};
}, [query]); // query가 변경될 때마다 effect 재실행
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="검색어 입력..."
/>
{loading && <div>검색 중...</div>}
<ul>
{results.map((result: any) => (
<li key={result.id}>{result.name}</li>
))}
</ul>
</div>
);
}useEffect のクリーンアップ関数で controller.abort() を呼び、アンマウント時や依存値(ここでは query)の変更により新しいリクエストを送る前に、前のリクエストをキャンセルするのが一般的なパターンです。Fetch APIでも同じように signal オプションを渡します。キャンセルされると、Fetchは名前が「AbortError」のエラーを発生させます。
// Apollo Client 사용 예시 (설정은 생략)
import React from 'react';
import { useQuery, gql } from '@apollo/client';
const GET_USER = gql` /* ... GraphQL 쿼리 ... */ `;
interface UserData { /* ... */ }
interface UserVars { /* ... */ }
function UserProfileApollo({ userId }: { userId: string }) {
const { loading, error, data } = useQuery<UserData, UserVars>(GET_USER, {
variables: { userId },
});
// ... 로딩, 에러, 데이터 처리 ...
}
if (loading) ...)で扱う代わりに、Reactの機能であるuseQuery フックで suspense: true を指定すると、読み込み中にPromiseをthrowし、Suspenseが検知してfallbackを表示できるようになります。import React, { Suspense } from 'react'; // Suspense 임포트
import { useQuery, QueryErrorResetBoundary } from 'react-query'; // QueryErrorResetBoundary 추가 (에러 처리용)
import { ErrorBoundary } from 'react-error-boundary'; // 에러 바운더리 라이브러리 예시
import axios from 'axios';
interface User { id: string; name: string; email: string; }
const fetchUser = async (userId: string): Promise<User> => {
const { data } = await axios.get(`/api/users/${userId}`);
return data;
};
// 데이터를 보여주는 실제 컴포넌트
function UserProfileData({ userId }: { userId: string }) {
const { data } = useQuery<User, Error>(
['user', userId],
() => fetchUser(userId),
{
suspense: true, // Suspense 모드 활성화
// useErrorBoundary: true, // ErrorBoundary와 함께 사용 시 권장
}
);
// isLoading, error 상태를 직접 처리할 필요 없음
return (
<div>
<h2>{data.name}</h2>
<p>Email: {data.email}</p>
</div>
);
}
// 에러 발생 시 보여줄 컴포넌트
function ErrorFallback({ error, resetErrorBoundary }: any) {
return (
<div role="alert">
<p>문제가 발생했습니다:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>다시 시도</button>
</div>
);
}
// Suspense와 ErrorBoundary로 감싸서 사용
function UserProfileSuspense({ userId }: { userId: string }) {
return (
// ErrorBoundary로 에러 처리 (Suspense는 로딩 상태만 처리)
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={reset}>
{/* Suspense로 로딩 상태 처리 */}
<Suspense fallback={<div>사용자 정보 로딩 중...</div>}>
<UserProfileData userId={userId} />
</Suspense>
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
}次回は、フロントエンドのセキュリティと安定性を学びます。XSSやCSRFなどの脅威とその予防策、認証と認可の扱い方を取り上げます。