跳到主要内容

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 APIAxiosXMLHttpRequest
浏览器支持现代浏览器全部全部
请求取消支持 (AbortController)支持 (CancelToken)支持
请求超时需要自行实现内置支持需要自行实现
自动转换JSON需要手动处理自动支持需要手动处理
拦截器需要自行实现内置支持需要自行实现
TypeScript支持原生支持完善支持原生支持
文件上传进度不支持支持支持
包大小0 (原生)~12KB min+gzip0 (原生)
学习曲线简单中等较陡
Node.js支持需要 polyfill原生支持不支持

3. 最佳实践建议

  1. 选择建议

    • 简单项目:使用 Fetch API
    • 大型项目:使用 Axios(功能更完善,生态更好)
    • React项目:结合 React Query 或 SWR 使用
  2. 封装建议

    • 统一处理错误响应
    • 添加请求/响应拦截器
    • 实现请求超时设置
    • 统一处理 loading 状态
    • 实现请求重试机制
    • 添加请求缓存机制
  3. React 项目特定建议

    • 使用自定义 Hook 封装请求逻辑
    • 考虑使用 React Query 或 SWR 进行数据请求管理
    • 实现请求状态管理(loading、error、data)
    • 添加请求防抖/节流
    • 实现请求缓存和数据预加载
  4. 安全建议

    • 添加 CSRF Token
    • 使用 HTTPS
    • 实现请求签名
    • 敏感数据加密
    • 添加请求超时限制