JavaScript HTTP 请求方案总结
1. React 中的 HTTP 请求封装
TypeScript 版本
// types.ts
export interface RequestConfig extends RequestInit {
timeout?: number;
}
export interface Response<T = any> {
data: T;
status: number;
statusText: string;
headers: Headers;
}
export interface HttpClient {
get<T>(url: string, config?: RequestConfig): Promise<T>;
post<T>(url: string, data?: any, config?: RequestConfig): Promise<T>;
put<T>(url: string, data?: any, config?: RequestConfig): Promise<T>;
delete<T>(url: string, config?: RequestConfig): Promise<T>;
}
// httpClient.ts
import { RequestConfig, Response, HttpClient } from './types';
class HttpService implements HttpClient {
private baseURL: string;
private defaultConfig: RequestConfig;
constructor(baseURL: string = '', defaultConfig: RequestConfig = {}) {
this.baseURL = baseURL;
this.defaultConfig = {
headers: {
'Content-Type': 'application/json',
},
...defaultConfig,
};
}
private async request<T>(url: string, config: RequestConfig): Promise<T> {
const fullURL = `${this.baseURL}${url}`;
const mergedConfig = {
...this.defaultConfig,
...config,
headers: {
...this.defaultConfig.headers,
...config.headers,
},
};
try {
const response = await fetch(fullURL, mergedConfig);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data as T;
} catch (error) {
console.error('Request failed:', error);
throw error;
}
}
public async get<T>(url: string, config: RequestConfig = {}): Promise<T> {
return this.request<T>(url, { ...config, method: 'GET' });
}
public async post<T>(url: string, data?: any, config: RequestConfig = {}): Promise<T> {
return this.request<T>(url, {
...config,
method: 'POST',
body: JSON.stringify(data),
});
}
public async put<T>(url: string, data?: any, config: RequestConfig = {}): Promise<T> {
return this.request<T>(url, {
...config,
method: 'PUT',
body: JSON.stringify(data),
});
}
public async delete<T>(url: string, config: RequestConfig = {}): Promise<T> {
return this.request<T>(url, { ...config, method: 'DELETE' });
}
}
// React Hook
import { useState, useEffect } from 'react';
interface UseHttpResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => void;
}
export function useHttp<T>(url: string, options: RequestConfig = {}): UseHttpResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null);
const httpClient = new HttpService();
const fetchData = async () => {
try {
setLoading(true);
const result = await httpClient.get<T>(url, options);
setData(result);
setError(null);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [url]);
return { data, loading, error, refetch: fetchData };
}
// 使用示例
interface User {
id: number;
name: string;
email: string;
}
function UserComponent() {
const { data, loading, error } = useHttp<User>('/api/user/1');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return null;
return (
<div>
<h1>{data.name}</h1>
<p>{data.email}</p>
</div>
);
}
JavaScript 版本
// httpClient.js
class HttpService {
constructor(baseURL = '', defaultConfig = {}) {
this.baseURL = baseURL;
this.defaultConfig = {
headers: {
'Content-Type': 'application/json',
},
...defaultConfig,
};
}
async request(url, config) {
const fullURL = `${this.baseURL}${url}`;
const mergedConfig = {
...this.defaultConfig,
...config,
headers: {
...this.defaultConfig.headers,
...config.headers,
},
};
try {
const response = await fetch(fullURL, mergedConfig);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Request failed:', error);
throw error;
}
}
get(url, config = {}) {
return this.request(url, { ...config, method: 'GET' });
}
post(url, data, config = {}) {
return this.request(url, {
...config,
method: 'POST',
body: JSON.stringify(data),
});
}
put(url, data, config = {}) {
return this.request(url, {
...config,
method: 'PUT',
body: JSON.stringify(data),
});
}
delete(url, config = {}) {
return this.request(url, { ...config, method: 'DELETE' });
}
}
// React Hook
import { useState, useEffect } from 'react';
export function useHttp(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const httpClient = new HttpService();
const fetchData = async () => {
try {
setLoading(true);
const result = await httpClient.get(url, options);
setData(result);
setError(null);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [url]);
return { data, loading, error, refetch: fetchData };
}
// 使用示例
function UserComponent() {
const { data, loading, error } = useHttp('/api/user/1');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return null;
return (
<div>
<h1>{data.name}</h1>
<p>{data.email}</p>
</div>
);
}
2. HTTP 请求库对比
特性 | Fetch API | Axios | XMLHttpRequest |
---|---|---|---|
浏览器支持 | 现代浏览器 | 全部 | 全部 |
请求取消 | 支持 (AbortController) | 支持 (CancelToken) | 支持 |
请求超时 | 需要自行实现 | 内置支 持 | 需要自行实现 |
自动转换JSON | 需要手动处理 | 自动支持 | 需要手动处理 |
拦截器 | 需要自行实现 | 内置支持 | 需要自行实现 |
TypeScript支持 | 原生支持 | 完善支持 | 原生支持 |
文件上传进度 | 不支持 | 支持 | 支持 |
包大小 | 0 (原生) | ~12KB min+gzip | 0 (原生) |
学习曲线 | 简单 | 中等 | 较陡 |
Node.js支持 | 需要 polyfill | 原生支持 | 不支持 |
3. 最佳实践建议
-
选择建议:
- 简单项目:使用 Fetch API
- 大型项目:使用 Axios(功能更完善,生态更好)
- React项目:结合 React Query 或 SWR 使用
-
封装建议:
- 统一处理错误响应
- 添加请求/响应拦截器
- 实现请求超时设置
- 统一处理 loading 状态
- 实现请求重试机制
- 添加请求缓存机制
-
React 项目特定建议:
- 使用自定义 Hook 封装请求逻辑
- 考虑使用 React Query 或 SWR 进行数据请求管理
- 实现请求状态管理(loading、error、data)
- 添加请求防抖/节流
- 实现请求缓存和数据预加载
-
安全建议:
- 添加 CSRF Token
- 使用 HTTPS
- 实现请求签名
- 敏感数据加密
- 添加请求超时限制