几乎每个前端项目都有一套 HTTP 请求封装,类似 axios 的请求配置:url、method、params、data、headers、timeout、responseType 等。用泛型约束响应数据类型,可以让请求函数返回的数据直接带有正确的类型,免去手动类型断言。同时,请求配置本身也需要类型化:method 用字面量联合约束、params/data 泛型化、headers 用 Record 定义结构,让整个请求链路类型安全闭环。
// 请求配置类型封装:类似 axios 的请求配置 + 泛型响应类型
// method 字面量约束 + params/data 泛型 + responseType 联合
// 1. HTTP 方法字面量联合
type HttpMethod =
| 'GET'
| 'POST'
| 'PUT'
| 'DELETE'
| 'PATCH'
| 'HEAD'
| 'OPTIONS';
// 2. 响应类型
type ResponseType =
| 'json'
| 'text'
| 'blob'
| 'arraybuffer'
| 'document';
// 3. 请求头类型
type HeadersMap = Record<string, string | number | boolean | undefined>;
// 4. 请求配置:泛型 P=params, D=data
interface RequestConfig<P = any, D = any> {
url: string;
method?: HttpMethod;
baseURL?: string;
params?: P; // URL 查询参数
data?: D; // 请求体数据
headers?: HeadersMap;
timeout?: number; // 超时时间,毫秒
responseType?: ResponseType;
withCredentials?: boolean;
signal?: AbortSignal; // 取消请求
}
// 5. 响应数据结构
interface ApiResponse<T = any> {
data: T;
status: number;
statusText: string;
headers: Record<string, string>;
config: RequestConfig;
}
// 6. 业务统一响应(成功/失败联合)
type BizResponse<T> =
| { code: 0; message: string; data: T; success: true }
| { code: number; message: string; data: null; success: false };
// 7. 请求函数类型:泛型 T 约束响应 data 类型
interface RequestFunction {
<T = any, P = any, D = any>(config: RequestConfig<P, D>): Promise<BizResponse<T>>;
get<T = any, P = any>(url: string, config?: Omit<RequestConfig<P, never>, 'url' | 'method'>): Promise<BizResponse<T>>;
post<T = any, D = any>(url: string, data?: D, config?: Omit<RequestConfig<never, D>, 'url' | 'method' | 'data'>): Promise<BizResponse<T>>;
}
// 8. 使用示例:用户 API
interface User {
id: number;
name: string;
email: string;
}
interface UserListParams {
page: number;
pageSize: number;
keyword?: string;
}
interface UserListData {
list: User[];
total: number;
}
// 声明请求函数(此处为类型示意,实现略)
declare const request: RequestFunction;
// GET 请求:params 受 UserListParams 约束
function fetchUsers(params: UserListParams) {
return request.get<UserListData, UserListParams>('/api/users', { params });
}
// POST 请求:data 受 User 约束
function createUser(data: User) {
return request.post<User, User>('/api/users', data);
}
HttpMethod:用 'GET' | 'POST' | ... 约束 method 字段,传错拼写或小写直接报错,比 string 安全得多。RequestConfig<P, D>:P 是 params 类型、D 是 data 类型,分别约束 URL 查询参数和请求体,不同请求可以传入不同的参数类型。BizResponse<T>:业务响应的 data 字段用泛型 T 参数化,调用方传入返回值类型后,响应数据直接带有正确类型,无需 as 断言。RequestFunction:主函数 + get/post 快捷方法,利用 Omit 排除已由参数传入的字段(如 url、method、data),配置对象更简洁。res.success 后 TS 自动收窄 data 的类型,成功时是 T、失败时是 null。// 响应数据类型自动推导
async function demo() {
const res = await fetchUsers({ page: 1, pageSize: 10 });
if (res.success) {
res.data.list; // User[] 类型,自动提示
res.data.total; // number 类型
} else {
res.data; // null,无法访问 list/total
res.message; // string,错误信息
}
}
// params 传错字段会被 TS 拦截
const badParams: UserListParams = {
page: 1,
pageSize: 10,
// keywordd: 'a' // 错误:keywordd 不存在于 UserListParams
};
// 自定义请求配置
const config: RequestConfig<UserListParams, never> = {
url: '/api/users',
method: 'GET',
params: { page: 1, pageSize: 20 },
timeout: 5000,
responseType: 'json',
};
// 响应类型推导
type Res = BizResponse<User>;
// => { code: 0; message: string; data: User; success: true }
// | { code: number; message: string; data: null; success: false }