TypeScript3분
axios 기반 라이브러리 제작 - 2 npm 배포
2025년 8월 3일
React Easy API
음 axios로 쉽게 통신 할 수 있도록 라이브러리를 만들어봤는데 주요 기능에 대한 내용을 정리해보았다.
📋 목차
🔍 개요
react-easy-api는 React 애플리케이션에서 Axios를 사용한 API 요청을 간편하게 처리할 수 있도록 도와주는 커스텀 훅 라이브러리이다. TypeScript를 완벽 지원하며, 로딩 상태 관리, 에러 핸들링, 응답 데이터 변환 등의 기능을 제공한다.
🎯 해결하는 문제
- ✅ 반복적인 상태 관리 - loading, error, data 상태를 자동으로 관리
- ✅ 일관된 에러 처리 - 통합된 에러 형식 제공
- ✅ 타입 안정성 - TypeScript 완벽 지원
- ✅ 코드 중복 제거 - 재사용 가능한 API 훅 생성
- ✅ 유연한 설정 - 런타임 요청 설정 변경 가능
📊 프로젝트 정보
| 항목 | 내용 |
|---|---|
| 패키지명 | react-easy-api |
| 버전 | 1.0.0 |
| 라이센스 | KimByeongNyeon |
| 작성자 | KimByeongNyeon |
| GitHub | KimByeongNyeon/react-easy-api |
| 언어 | TypeScript |
| 프레임워크 | React |
⭐ 주요 특징
🔧 기술적 특징
- TypeScript 완벽 지원: 강력한 타입 안정성과 IntelliSense 지원
- React Hooks 기반: 최신 React 패턴 적용
- Axios 통합: 업계 표준 HTTP 클라이언트 사용
- ESM & CommonJS 지원: 다양한 번들러 환경에서 사용 가능
- Zero Dependencies: React와 Axios 외 추가 의존성 없음
🎨 개발자 경험
- 직관적인 API: 학습 곡선이 낮은 간단한 인터페이스
- 풍부한 예제: 다양한 사용 사례 제공
- 상세한 문서: 완전한 API 레퍼런스
- TypeScript 지원: 컴파일 타임 타입 체킹
🚀 성능 특징
- 자동 메모이제이션: useCallback으로 불필요한 리렌더링 방지
- 경량화: 최소한의 번들 사이즈
- 트리 쉐이킹: 사용하지 않는 코드 자동 제거
📦 설치 및 설정
1. 패키지 설치
bash# npm 사용
npm install react-easy-api axios react
# yarn 사용
yarn add react-easy-api axios react
# pnpm 사용
pnpm add react-easy-api axios react
2. 필수 요구사항
| 패키지 | 최소 버전 | 설명 |
|---|---|---|
| React | ≥16.8.0 | Hooks 지원 버전 |
| Axios | ≥0.21.0 | HTTP 클라이언트 |
3. TypeScript 설정 (선택사항)
json// tsconfig.json
{
"compilerOptions": {
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true
}
}
🚀 기본 사용법
1. Axios 인스턴스 설정
typescript// api/client.ts
import axios from 'axios';
export const apiClient = axios.create({
baseURL: '<https://api.example.com>',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// 요청 인터셉터
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 응답 인터셉터
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// 토큰 만료 처리
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
2. 기본 GET 요청
typescriptimport React, { useEffect } from 'react';
import { useApi } from 'react-easy-api';
import { apiClient } from './api/client';
interface User {
id: number;
name: string;
email: string;
}
function UserList() {
const { loading, error, data, execute } = useApi<User[]>({
endpoint: '/users',
method: 'GET',
axiosInstance: apiClient,
});
useEffect(() => {
execute();
}, [execute]);
if (loading) return <div>로딩 중...</div>;
if (error) return <div>에러: {error.message}</div>;
if (!data) return <div>데이터가 없습니다</div>;
return (
<ul>
{data.map(user => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
);
}
3. POST 요청
typescriptinterface CreateUserPayload {
name: string;
email: string;
}
function CreateUser() {
const { loading, error, data, execute } = useApi<User, CreateUserPayload>({
endpoint: '/users',
method: 'POST',
axiosInstance: apiClient,
onSuccess: (user) => {
console.log('사용자 생성 성공:', user);
// 성공 후 처리 로직
},
onError: (error) => {
console.error('사용자 생성 실패:', error);
// 에러 처리 로직
},
});
const handleSubmit = async (formData: CreateUserPayload) => {
await execute(formData);
};
return (
<div>
{/* 폼 컴포넌트 */}
<button onClick={() => handleSubmit({name: '김철수', email: 'kim@example.com'})} disabled={loading}>
{loading ? '생성 중...' : '사용자 생성'}
</button>
{error && <p style={{ color: 'red' }}>에러: {error.message}</p>}
{data && <p>생성된 사용자: {data.name}</p>}
</div>
);
}
🔥 고급 사용법
1. 응답 포맷터 사용
typescriptimport { useApi, responseFormatters } from 'react-easy-api';
// 표준 API 응답 형식
interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
}
function UserListWithFormatter() {
const { data, execute } = useApi<ApiResponse<User[]>, void, User[]>({
endpoint: '/users',
axiosInstance: apiClient,
responseFormatter: responseFormatters.standard, // data 필드만 추출
});
// 이제 data는 User[] 타입입니다 (ApiResponse<User[]>가 아닌)
}
// 커스텀 응답 포맷터
function UserListWithCustomFormatter() {
const { data, execute } = useApi<User[], User[]>({
endpoint: '/users',
axiosInstance: apiClient,
responseFormatter: (rawData: User[]) => {
return rawData
.filter(user => user.email.includes('@company.com'))
.map(user => ({
...user,
displayName: `${user.name} (${user.email})`,
}));
},
});
}
2. 동적 요청 설정
typescriptimport { RequestConfig } from 'react-easy-api';
function DynamicUserSearch() {
const [searchTerm, setSearchTerm] = useState('');
const [page, setPage] = useState(1);
const { loading, error, data, execute } = useApi<User[]>({
endpoint: '/users', // 기본 엔드포인트
axiosInstance: apiClient,
});
const handleSearch = () => {
const config: RequestConfig = {
url: '/users/search', // 다른 엔드포인트 사용
params: {
q: searchTerm,
page: page,
limit: 10,
},
headers: {
'X-Search-Type': 'advanced',
},
timeout: 15000, // 더 긴 타임아웃
};
execute(undefined, config);
};
return (
<div>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="사용자 검색..."
/>
<button onClick={handleSearch} disabled={loading}>
검색
</button>
</div>
);
}
3. createApiHook을 사용한 재사용 가능한 훅
typescriptimport { createApiHook } from 'react-easy-api';
// 사용자 API용 베이스 훅 생성
const useUserApi = createApiHook<User>({
method: 'GET',
axiosInstance: apiClient,
enableLogging: true,
onError: (error) => {
// 공통 에러 처리
if (error.status === 404) {
console.log('사용자를 찾을 수 없습니다');
}
},
});
// 특정 사용자 정보 가져오기
function UserProfile({ userId }: { userId: number }) {
const { loading, error, data, execute } = useUserApi(`/users/${userId}`);
useEffect(() => {
execute();
}, [execute, userId]);
// 컴포넌트 렌더링...
}
// 사용자 목록 가져오기 (추가 옵션 포함)
function UserManagement() {
const { loading, error, data, execute } = useUserApi('/users', {
responseFormatter: (users: User[]) => users.filter(u => u.active),
onSuccess: (users) => {
console.log(`${users.length}명의 활성 사용자를 찾았습니다`);
},
});
}
4. 에러 처리 및 재시도
typescriptfunction RobustApiCall() {
const [retryCount, setRetryCount] = useState(0);
const maxRetries = 3;
const { loading, error, data, execute, reset } = useApi<User[]>({
endpoint: '/users',
axiosInstance: apiClient,
onError: (error) => {
console.error('API 에러:', error);
// 특정 에러에 대한 자동 재시도
if (error.code === 'NETWORK_ERROR' && retryCount < maxRetries) {
setTimeout(() => {
setRetryCount(prev => prev + 1);
execute();
}, 1000 * (retryCount + 1)); // 지수 백오프
}
},
onSuccess: () => {
setRetryCount(0); // 성공시 재시도 카운트 리셋
},
});
const handleRetry = () => {
reset(); // 이전 에러 상태 클리어
setRetryCount(0);
execute();
};
return (
<div>
{loading && <p>로딩 중... {retryCount > 0 && `(재시도 ${retryCount}/${maxRetries})`}</p>}
{error && (
<div>
<p>에러 발생: {error.message}</p>
{error.status && <p>상태 코드: {error.status}</p>}
<button onClick={handleRetry}>다시 시도</button>
</div>
)}
{data && <UserList users={data} />}
</div>
);
}
📚 API 레퍼런스
useApi 훅
typescriptfunction useApi<T, P = void, R = T>(
options: UseApiOptions<T, R>
): UseApiReturn<R, P>
매개변수
UseApiOptions<T, R>
| 속성 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
endpoint | string | ✅ | - | API 엔드포인트 URL |
method | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | ❌ | 'GET' | HTTP 메서드 |
axiosInstance | AxiosInstance | ✅ | - | 사용할 Axios 인스턴스 |
responseFormatter | (data: T) => R | ❌ | undefined | 응답 데이터 변환 함수 |
onSuccess | (data: R) => void | ❌ | undefined | 성공 콜백 함수 |
onError | (error: ApiError) => void | ❌ | undefined | 에러 콜백 함수 |
enableLogging | boolean | ❌ | false | 콘솔 로그 활성화 여부 |
반환값
UseApiReturn<R, P>
| 속성 | 타입 | 설명 |
|---|---|---|
loading | boolean | 요청 진행 중 여부 |
error | ApiError | null | 에러 정보 (없으면 null) |
data | R | null | 응답 데이터 (없으면 null) |
execute | (payload?: P, config?: RequestConfig) => Promise<R | null> | API 요청 실행 함수 |
reset | () => void | 상태 초기화 함수 |
타입 정의
ApiError
typescriptinterface ApiError {
message: string; // 에러 메시지
status?: number; // HTTP 상태 코드
code?: string; // 에러 코드
details?: unknown; // 추가 에러 정보
}
RequestConfig
typescriptinterface RequestConfig {
url?: string; // 요청 URL (기본 endpoint 대신 사용)
headers?: Record<string, string>; // 추가 헤더
params?: Record<string, unknown>; // 쿼리 파라미터
timeout?: number; // 타임아웃 (ms)
}
유틸리티 함수
responseFormatters
미리 정의된 응답 포맷터 컬렉션:
typescriptconst responseFormatters = {
// 표준 API 응답에서 data 필드 추출
standard: <T>(response: { data: T; message: string; success: boolean }) => response.data,
// FastAPI 응답 처리
fastApi: <T>(response: T | { error: string }) => {
if (typeof response === "object" && response !== null && "error" in response) {
throw new Error(response.error);
}
return response;
},
// 페이지네이션 응답 유지
paginated: <T>(response: { items: T[]; total: number; page: number; size: number }) => response,
// 배열 데이터 추출
arrayData: <T>(response: { results: T[] }) => response.results,
};
createApiHook
재사용 가능한 API 훅 생성기:
typescriptfunction createApiHook<T, P = void, R = T>(
baseOptions: Omit<UseApiOptions<T, R>, 'endpoint'>
) => (
endpoint: string,
additionalOptions?: Partial<UseApiOptions<T, R>>
) => UseApiReturn<R, P>
🎯 실전 예제
1. 사용자 관리 시스템
typescript// types/user.ts
export interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
avatar?: string;
createdAt: string;
updatedAt: string;
}
export interface CreateUserRequest {
name: string;
email: string;
role: 'admin' | 'user';
}
export interface UpdateUserRequest extends Partial<CreateUserRequest> {
avatar?: File;
}
// hooks/useUserApi.ts
import { createApiHook } from 'react-easy-api';
import { apiClient } from '../api/client';
import { User } from '../types/user';
export const useUserApi = createApiHook<User>({
axiosInstance: apiClient,
enableLogging: process.env.NODE_ENV === 'development',
onError: (error) => {
// 글로벌 에러 처리
if (error.status === 403) {
console.error('권한이 없습니다');
}
},
});
// components/UserManagement.tsx
import React, { useState, useEffect } from 'react';
import { useApi } from 'react-easy-api';
import { useUserApi } from '../hooks/useUserApi';
import { User, CreateUserRequest } from '../types/user';
function UserManagement() {
const [users, setUsers] = useState<User[]>([]);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
// 사용자 목록 조회
const {
loading: loadingUsers,
error: usersError,
data: usersData,
execute: fetchUsers
} = useUserApi('/users');
// 사용자 생성
const {
loading: creatingUser,
error: createError,
execute: createUser
} = useApi<User, CreateUserRequest>({
endpoint: '/users',
method: 'POST',
axiosInstance: apiClient,
onSuccess: (newUser) => {
setUsers(prev => [...prev, newUser]);
console.log('사용자가 생성되었습니다:', newUser);
},
});
// 사용자 삭제
const {
loading: deletingUser,
execute: deleteUser
} = useApi<void, void>({
endpoint: '/users/:id',
method: 'DELETE',
axiosInstance: apiClient,
onSuccess: () => {
if (selectedUser) {
setUsers(prev => prev.filter(u => u.id !== selectedUser.id));
setSelectedUser(null);
}
},
});
useEffect(() => {
fetchUsers();
}, [fetchUsers]);
useEffect(() => {
if (usersData) {
setUsers(usersData);
}
}, [usersData]);
const handleCreateUser = async (userData: CreateUserRequest) => {
await createUser(userData);
};
const handleDeleteUser = async (user: User) => {
if (window.confirm(`"${user.name}" 사용자를 삭제하시겠습니까?`)) {
await deleteUser(undefined, { url: `/users/${user.id}` });
}
};
return (
<div>
<h1>사용자 관리</h1>
{/* 사용자 목록 */}
{loadingUsers && <p>사용자 목록을 불러오는 중...</p>}
{usersError && <p>에러: {usersError.message}</p>}
<div style={{ display: 'flex', gap: '20px' }}>
<div style={{ flex: 1 }}>
<h2>사용자 목록</h2>
{users.map(user => (
<div key={user.id} style={{
padding: '10px',
border: '1px solid #ccc',
margin: '5px 0',
backgroundColor: selectedUser?.id === user.id ? '#e3f2fd' : 'white'
}}>
<h3>{user.name}</h3>
<p>이메일: {user.email}</p>
<p>역할: {user.role}</p>
<button onClick={() => setSelectedUser(user)}>선택</button>
<button
onClick={() => handleDeleteUser(user)}
disabled={deletingUser}
style={{ marginLeft: '10px' }}
>
{deletingUser ? '삭제 중...' : '삭제'}
</button>
</div>
))}
</div>
<div style={{ flex: 1 }}>
<UserForm
onSubmit={handleCreateUser}
loading={creatingUser}
error={createError}
/>
</div>
</div>
</div>
);
}
2. 실시간 데이터 동기화
typescript// hooks/useRealtimeData.ts
import { useApi } from 'react-easy-api';
import { useEffect, useRef } from 'react';
export function useRealtimeData<T>(
endpoint: string,
intervalMs: number = 5000
) {
const { loading, error, data, execute } = useApi<T>({
endpoint,
axiosInstance: apiClient,
enableLogging: false, // 실시간 요청은 로그 비활성화
});
const intervalRef = useRef<NodeJS.Timeout>();
useEffect(() => {
// 초기 데이터 로드
execute();
// 주기적 업데이트 설정
intervalRef.current = setInterval(() => {
execute();
}, intervalMs);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [execute, intervalMs]);
const refresh = () => {
execute();
};
return { loading, error, data, refresh };
}
// components/Dashboard.tsx
function Dashboard() {
const { loading, error, data: stats, refresh } = useRealtimeData<{
users: number;
orders: number;
revenue: number;
}>('/dashboard/stats', 10000); // 10초마다 업데이트
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h1>대시보드</h1>
<button onClick={refresh} disabled={loading}>
{loading ? '새로고침 중...' : '새로고침'}
</button>
</div>
{error && <p style={{ color: 'red' }}>데이터 로드 실패: {error.message}</p>}
{stats && (
<div style={{ display: 'flex', gap: '20px' }}>
<div style={{ padding: '20px', border: '1px solid #ccc' }}>
<h3>총 사용자</h3>
<p style={{ fontSize: '2em' }}>{stats.users.toLocaleString()}</p>
</div>
<div style={{ padding: '20px', border: '1px solid #ccc' }}>
<h3>총 주문</h3>
<p style={{ fontSize: '2em' }}>{stats.orders.toLocaleString()}</p>
</div>
<div style={{ padding: '20px', border: '1px solid #ccc' }}>
<h3>총 수익</h3>
<p style={{ fontSize: '2em' }}>${stats.revenue.toLocaleString()}</p>
</div>
</div>
)}
</div>
);
}
3. 파일 업로드
typescript// components/FileUpload.tsx
import { useApi } from 'react-easy-api';
import { useState } from 'react';
interface UploadResponse {
url: string;
filename: string;
size: number;
}
function FileUpload() {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [uploadProgress, setUploadProgress] = useState(0);
const { loading, error, data, execute } = useApi<UploadResponse, FormData>({
endpoint: '/upload',
method: 'POST',
axiosInstance: apiClient,
onSuccess: (response) => {
console.log('파일 업로드 성공:', response);
setSelectedFile(null);
setUploadProgress(0);
},
});
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
setSelectedFile(file);
}
};
const handleUpload = async () => {
if (!selectedFile) return;
const formData = new FormData();
formData.append('file', selectedFile);
formData.append('category', 'documents');
// 업로드 진행률 추적을 위한 커스텀 설정
const config = {
headers: {
'Content-Type': 'multipart/form-data',
},
timeout: 30000, // 30초 타임아웃
onUploadProgress: (progressEvent: any) => {
const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
setUploadProgress(progress);
},
};
await execute(formData, config);
};
return (
<div>
<h2>파일 업로드</h2>
<input
type="file"
onChange={handleFileSelect}
accept=".pdf,.doc,.docx,.jpg,.png"
/>
{selectedFile && (
<div style={{ margin: '10px 0' }}>
<p>선택된 파일: {selectedFile.name}</p>
<p>크기: {(selectedFile.size / 1024 / 1024).toFixed(2)} MB</p>
</div>
)}
<button
onClick={handleUpload}
disabled={!selectedFile || loading}
style={{ margin: '10px 0' }}
>
{loading ? '업로드 중...' : '업로드'}
</button>
{loading && (
<div style={{ margin: '10px 0' }}>
<div style={{
width: '100%',
backgroundColor: '#f0f0f0',
borderRadius: '4px',
overflow: 'hidden'
}}>
<div
style={{
width: `${uploadProgress}%`,
height: '20px',
backgroundColor: '#4caf50',
transition: 'width 0.3s ease',
}}
/>
</div>
<p>{uploadProgress}% 완료</p>
</div>
)}
{error && (
<p style={{ color: 'red' }}>
업로드 실패: {error.message}
</p>
)}
{data && (
<div style={{ marginTop: '10px', padding: '10px', backgroundColor: '#e8f5e8' }}>
<h3>업로드 성공!</h3>
<p>파일명: {data.filename}</p>
<p>크기: {(data.size / 1024 / 1024).toFixed(2)} MB</p>
<a href={data.url} target="_blank" rel="noopener noreferrer">
파일 보기
</a>
</div>
)}
</div>
);
}
💡 베스트 프랙티스
1. 효율적인 상태 관리
typescript// ❌ 잘못된 방법: 불필요한 리렌더링
function BadExample() {
const { data, execute } = useApi({
endpoint: '/users',
axiosInstance: apiClient,
onSuccess: (users) => {
// 매 렌더링마다 새로운 함수 생성
console.log('Success:', users);
},
});
}
// ✅ 올바른 방법: useCallback 사용
function GoodExample() {
const handleSuccess = useCallback((users: User[]) => {
console.log('Success:', users);
}, []);
const { data, execute } = useApi({
endpoint: '/users',
axiosInstance: apiClient,
onSuccess: handleSuccess,
});
}
2. 타입 안정성 확보
typescript// ✅ 제네릭을 활용한 타입 안정성
interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
}
interface User {
id: number;
name: string;
email: string;
}
// 명확한 타입 지정
const { data, execute } = useApi<ApiResponse<User[]>, void, User[]>({
endpoint: '/users',
axiosInstance: apiClient,
responseFormatter: responseFormatters.standard,
});
// data는 User[] 타입으로 추론됨
3. 에러 경계 활용
typescript// components/ErrorBoundary.tsx
import React from 'react';
interface Props {
children: React.ReactNode;
fallback?: React.ComponentType<{ error: Error }>;
}
interface State {
hasError: boolean;
error?: Error;
}
class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
const FallbackComponent = this.props.fallback || DefaultErrorFallback;
return <FallbackComponent error={this.state.error!} />;
}
return this.props.children;
}
}
function DefaultErrorFallback({ error }: { error: Error }) {
return (
<div style={{ padding: '20px', border: '1px solid red', borderRadius: '4px' }}>
<h2>오류가 발생했습니다</h2>
<p>{error.message}</p>
<button onClick={() => window.location.reload()}>페이지 새로고침</button>
</div>
);
}
4. API 응답 캐싱
typescript// utils/cache.ts
class ApiCache {
private cache = new Map<string, { data: any; timestamp: number }>();
private readonly TTL = 5 * 60 * 1000; // 5분
set(key: string, data: any) {
this.cache.set(key, {
data,
timestamp: Date.now(),
});
}
get(key: string) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp > this.TTL) {
this.cache.delete(key);
return null;
}
return item.data;
}
clear() {
this.cache.clear();
}
}
export const apiCache = new ApiCache();
// hooks/useCachedApi.ts
import { useApi } from 'react-easy-api';
import { apiCache } from '../utils/cache';
export function useCachedApi<T>(endpoint: string) {
const { loading, error, data, execute } = useApi<T>({
endpoint,
axiosInstance: apiClient,
onSuccess: (data) => {
apiCache.set(endpoint, data);
},
});
const executeWithCache = useCallback(async () => {
const cached = apiCache.get(endpoint);
if (cached) {
return cached;
}
return execute();
}, [endpoint, execute]);
return { loading, error, data, execute: executeWithCache };
}
🔧 트러블슈팅
1. 일반적인 문제들
"Module not found" 에러
bash# React와 Axios가 설치되지 않은 경우
npm install react axios
# TypeScript 타입이 없는 경우
npm install --save-dev @types/react
CORS 에러
typescript// 개발 환경에서 프록시 설정 (package.json)
{
"name": "my-app",
"proxy": "<http://localhost:3001>"
}
// 또는 Axios 인스턴스에서 설정
const apiClient = axios.create({
baseURL: process.env.NODE_ENV === 'development'
? '/api'
: '<https://api.production.com>',
});
무한 리렌더링
typescript// ❌ 문제: execute가 의존성 배열에 없음
useEffect(() => {
execute();
}, []); // execute가 변경되어도 재실행되지 않음
// ✅ 해결: execute를 의존성 배열에 추가
useEffect(() => {
execute();
}, [execute]);
// 또는 useCallback으로 execute 함수 메모이제이션 확인
2. 성능 최적화
불필요한 요청 방지
typescript// 조건부 실행
function ConditionalApi({ shouldFetch }: { shouldFetch: boolean }) {
const { data, execute } = useApi({
endpoint: '/expensive-data',
axiosInstance: apiClient,
});
useEffect(() => {
if (shouldFetch) {
execute();
}
}, [shouldFetch, execute]);
}
// 디바운싱
import { useDebouncedCallback } from 'use-debounce';
function SearchWithDebounce() {
const [query, setQuery] = useState('');
const { loading, data, execute } = useApi({
endpoint: '/search',
axiosInstance: apiClient,
});
const debouncedSearch = useDebouncedCallback(
(searchQuery: string) => {
if (searchQuery.length > 2) {
execute(undefined, { params: { q: searchQuery } });
}
},
500
);
useEffect(() => {
debouncedSearch(query);
}, [query, debouncedSearch]);
}
태그
#TypeScript#library#axios
