TypeScript 开发指南 / 18 - HTTP 请求
HTTP 请求
fetch API
基本用法
// fetch 返回 Promise<Response>
const response = await fetch("https://api.example.com/users");
// Response 对象
console.log(response.status); // number
console.log(response.statusText); // string
console.log(response.ok); // boolean
console.log(response.headers); // Headers
// 解析响应
const text = await response.text(); // string
const json = await response.json(); // any
const blob = await response.blob(); // Blob
const arrayBuffer = await response.arrayBuffer(); // ArrayBuffer
类型安全的 fetch
// 通用请求函数
async function request<T>(url: string, options?: RequestInit): Promise<T> {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
// GET 请求
interface User {
id: number;
name: string;
email: string;
}
const users = await request<User[]>("/api/users");
// users: User[]
const user = await request<User>("/api/users/1");
// user: User
GET 请求
async function get<T>(url: string, params?: Record<string, string>): Promise<T> {
const queryString = params
? "?" + new URLSearchParams(params).toString()
: "";
return request<T>(url + queryString);
}
// 使用
const users = await get<User[]>("/api/users", { page: "1", limit: "10" });
POST 请求
async function post<T, B = any>(url: string, body: B): Promise<T> {
return request<T>(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
}
// 使用
interface CreateUserDto {
name: string;
email: string;
}
const newUser = await post<User, CreateUserDto>("/api/users", {
name: "Alice",
email: "alice@example.com"
});
PUT / PATCH / DELETE
async function put<T, B = any>(url: string, body: B): Promise<T> {
return request<T>(url, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
}
async function patch<T, B = any>(url: string, body: B): Promise<T> {
return request<T>(url, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
}
async function del<T = void>(url: string): Promise<T> {
return request<T>(url, { method: "DELETE" });
}
// 使用
await put<User, Partial<User>>("/api/users/1", { name: "Bob" });
await patch<User, Partial<User>>("/api/users/1", { name: "Bob" });
await del("/api/users/1");
axios
安装
npm install axios
npm install -D @types/axios # 通常不需要,axios 自带类型
基本用法
import axios, { AxiosResponse, AxiosError } from "axios";
// GET
const response: AxiosResponse<User[]> = await axios.get("/api/users");
console.log(response.data); // User[]
console.log(response.status); // number
console.log(response.headers); // any
// POST
const created: AxiosResponse<User> = await axios.post("/api/users", {
name: "Alice",
email: "alice@example.com"
});
创建实例
const api = axios.create({
baseURL: "https://api.example.com",
timeout: 10000,
headers: {
"Content-Type": "application/json"
}
});
// 请求拦截器
api.interceptors.request.use((config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器
api.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response?.status === 401) {
// 跳转登录
}
return Promise.reject(error);
}
);
泛型封装
class ApiClient {
private client: typeof axios;
constructor(baseURL: string) {
this.client = axios.create({
baseURL,
timeout: 10000,
headers: { "Content-Type": "application/json" }
});
}
async get<T>(url: string, params?: any): Promise<T> {
const response = await this.client.get<T>(url, { params });
return response.data;
}
async post<T, B = any>(url: string, body: B): Promise<T> {
const response = await this.client.post<T>(url, body);
return response.data;
}
async put<T, B = any>(url: string, body: B): Promise<T> {
const response = await this.client.put<T>(url, body);
return response.data;
}
async delete<T = void>(url: string): Promise<T> {
const response = await this.client.delete<T>(url);
return response.data;
}
}
// 使用
const api = new ApiClient("https://api.example.com");
const users = await api.get<User[]>("/users");
通用 API 封装
响应类型
// 标准 API 响应格式
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// 分页响应
interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}
// 错误响应
interface ApiError {
code: number;
message: string;
details?: Record<string, string[]>;
}
完整的 API 客户端
class ApiClient {
private baseURL: string;
private headers: Record<string, string>;
constructor(baseURL: string) {
this.baseURL = baseURL;
this.headers = { "Content-Type": "application/json" };
}
setToken(token: string): void {
this.headers.Authorization = `Bearer ${token}`;
}
private async request<T>(
method: string,
path: string,
body?: any,
params?: Record<string, string>
): Promise<ApiResponse<T>> {
const url = new URL(path, this.baseURL);
if (params) {
Object.entries(params).forEach(([key, value]) => {
url.searchParams.set(key, value);
});
}
const response = await fetch(url.toString(), {
method,
headers: this.headers,
body: body ? JSON.stringify(body) : undefined
});
const data: ApiResponse<T> = await response.json();
if (!response.ok) {
throw new ApiRequestError(data.code, data.message);
}
return data;
}
async get<T>(path: string, params?: Record<string, string>): Promise<T> {
const response = await this.request<T>("GET", path, undefined, params);
return response.data;
}
async post<T, B = any>(path: string, body: B): Promise<T> {
const response = await this.request<T>("POST", path, body);
return response.data;
}
async put<T, B = any>(path: string, body: B): Promise<T> {
const response = await this.request<T>("PUT", path, body);
return response.data;
}
async delete<T = void>(path: string): Promise<T> {
const response = await this.request<T>("DELETE", path);
return response.data;
}
}
class ApiRequestError extends Error {
constructor(
public code: number,
message: string
) {
super(message);
this.name = "ApiRequestError";
}
}
资源 API 基类
abstract class ResourceApi<T, CreateDto, UpdateDto> {
constructor(
protected client: ApiClient,
protected resourcePath: string
) {}
async getAll(params?: Record<string, string>): Promise<T[]> {
return this.client.get<T[]>(this.resourcePath, params);
}
async getById(id: number): Promise<T> {
return this.client.get<T>(`${this.resourcePath}/${id}`);
}
async create(data: CreateDto): Promise<T> {
return this.client.post<T, CreateDto>(this.resourcePath, data);
}
async update(id: number, data: UpdateDto): Promise<T> {
return this.client.put<T, UpdateDto>(`${this.resourcePath}/${id}`, data);
}
async delete(id: number): Promise<void> {
return this.client.delete(`${this.resourcePath}/${id}`);
}
}
// 使用
interface User {
id: number;
name: string;
email: string;
}
interface CreateUserDto {
name: string;
email: string;
password: string;
}
interface UpdateUserDto {
name?: string;
email?: string;
}
class UserApi extends ResourceApi<User, CreateUserDto, UpdateUserDto> {
constructor(client: ApiClient) {
super(client, "/users");
}
async getByEmail(email: string): Promise<User | null> {
const users = await this.getAll({ email });
return users[0] || null;
}
}
错误处理
// 统一错误处理
class ApiError extends Error {
constructor(
public status: number,
public code: string,
message: string,
public details?: Record<string, string[]>
) {
super(message);
this.name = "ApiError";
}
}
class NetworkError extends Error {
constructor(message: string = "网络连接失败") {
super(message);
this.name = "NetworkError";
}
}
class TimeoutError extends Error {
constructor(message: string = "请求超时") {
super(message);
this.name = "TimeoutError";
}
}
// 包装请求函数
async function safeRequest<T>(
requestFn: () => Promise<T>
): Promise<[T | null, Error | null]> {
try {
const data = await requestFn();
return [data, null];
} catch (error) {
if (error instanceof ApiError) {
return [null, error];
}
if (error instanceof TypeError && error.message === "Failed to fetch") {
return [null, new NetworkError()];
}
return [null, error as Error];
}
}
// 使用
const [users, error] = await safeRequest(() => api.get<User[]>("/users"));
if (error) {
console.error(error.message);
} else {
console.log(users!.length);
}
业务场景:类型安全的 REST API
// API 路由类型映射
interface ApiRoutes {
"/users": {
GET: { params: { page?: string; limit?: string }; response: User[] };
POST: { body: CreateUserDto; response: User };
};
"/users/:id": {
GET: { response: User };
PUT: { body: UpdateUserDto; response: User };
DELETE: { response: void };
};
"/posts": {
GET: { params: { userId?: string }; response: Post[] };
POST: { body: CreatePostDto; response: Post };
};
}
// 类型安全的请求函数
type ExtractResponse<T> = T extends { response: infer R } ? R : never;
type ExtractBody<T> = T extends { body: infer B } ? B : never;
class TypedApiClient {
async get<P extends keyof ApiRoutes>(
path: P,
params?: ApiRoutes[P]["GET"] extends { params: infer P } ? P : never
): Promise<ExtractResponse<ApiRoutes[P]["GET"]>> {
// 实现
return {} as any;
}
async post<P extends keyof ApiRoutes>(
path: P,
body: ExtractBody<ApiRoutes[P]["POST"]>
): Promise<ExtractResponse<ApiRoutes[P]["POST"]>> {
return {} as any;
}
}
const api = new TypedApiClient();
const users = await api.get("/users", { page: "1" }); // User[]
const user = await api.post("/users", { name: "Alice", email: "a@b.com", password: "123" }); // User
注意事项
fetch 不会自动 reject 非 2xx 响应——需要手动检查 response.okfetch 返回的 response.json() 是 any——需要手动断言或使用泛型- axios 自动 reject 非 2xx 响应——通过
try-catch 捕获错误 - 始终处理错误——网络请求是最容易出错的地方
- 使用拦截器统一处理认证和错误
扩展阅读