TypeScript 泛型高级用法:封装类型安全的请求工具函数(附类型定义)
·
TypeScript 泛型高级用法:封装类型安全的请求工具函数
在 TypeScript 中,泛型(Generics)是实现类型安全的关键工具,尤其在处理网络请求时。通过高级泛型技术,我们可以封装一个请求工具函数,确保输入参数、响应数据和错误处理都严格类型化。这能显著减少运行时错误,提升代码可维护性。下面我将逐步构建一个类型安全的请求工具函数,涵盖泛型约束、条件类型和映射类型等高级用法。最终提供一个完整的实现(附类型定义)。
步骤 1: 理解需求和基本结构
我们需要一个工具函数,支持以下特性:
- 指定请求方法(如 GET、POST)。
- 类型化请求参数(如查询字符串或请求体)。
- 类型化响应数据(通过泛型指定)。
- 统一错误处理(类型化错误信息)。
基本函数签名可定义为:
async function request<T>(url: string, options: RequestOptions): Promise<T>
其中:
- $T$ 是泛型参数,代表期望的响应数据类型。
RequestOptions是自定义类型,用于封装请求配置。
步骤 2: 定义核心类型
使用 TypeScript 的高级泛型特性,定义以下类型来确保类型安全:
- 请求参数类型:使用泛型约束和映射类型,处理不同 HTTP 方法(如 GET 无请求体,POST 有请求体)。
- 响应类型:通过 $T$ 泛型动态指定。
- 错误类型:自定义错误接口,统一处理异常。
类型定义如下:
// 定义错误类型,确保错误信息结构化
interface RequestError {
message: string;
status: number;
}
// 定义请求选项类型,使用泛型约束
type RequestOptions<D = unknown> = {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; // 支持常见 HTTP 方法
headers?: Record<string, string>; // 请求头类型化
data?: D; // 请求体数据,泛型 D 指定其类型
};
// 定义响应类型:使用条件类型处理成功和失败
type ResponseResult<T> = T | RequestError;
步骤 3: 实现请求工具函数
结合泛型高级用法,我们实现函数:
- 使用
fetchAPI 作为基础(确保浏览器兼容)。 - 通过泛型 $T$ 和 $D$ 分别指定响应类型和请求体类型。
- 添加错误处理逻辑,返回类型化错误。
async function request<T, D = unknown>(
url: string,
options: RequestOptions<D> = {}
): Promise<ResponseResult<T>> {
try {
const { method = 'GET', headers = {}, data } = options;
const config: RequestInit = {
method,
headers: {
'Content-Type': 'application/json',
...headers,
},
};
// 处理请求体:仅当方法为 POST/PUT 且 data 存在时添加
if ((method === 'POST' || method === 'PUT') && data !== undefined) {
config.body = JSON.stringify(data);
}
const response = await fetch(url, config);
if (!response.ok) {
// 返回类型化错误
const error: RequestError = {
message: `Request failed with status ${response.status}`,
status: response.status,
};
return error;
}
// 解析响应数据,并断言为泛型 T 类型
const result: T = await response.json();
return result;
} catch (error) {
// 捕获网络错误,统一类型化
const defaultError: RequestError = {
message: error instanceof Error ? error.message : 'Unknown error',
status: 500,
};
return defaultError;
}
}
步骤 4: 高级泛型用法解析
- 泛型约束:在
RequestOptions<D>中,$D$ 允许动态指定请求体类型,例如 $D$ 可以是用户定义的接口。 - 条件类型:
ResponseResult<T>使用联合类型,确保函数返回 $T$ 或RequestError,便于调用者处理。 - 默认泛型参数:$D = unknown$ 提供灵活性,当不传请求体时使用默认值。
- 类型安全保证:通过
as T断言(在真实场景中,应结合 Zod 或 io-ts 进行运行时验证),确保响应数据匹配 $T$。
步骤 5: 使用示例
调用工具函数时,传入泛型参数指定类型:
// 定义用户数据的接口
interface User {
id: number;
name: string;
}
// 示例 1: GET 请求,获取用户列表(响应类型为 User[])
async function fetchUsers(): Promise<User[] | RequestError> {
return request<User[]>('https://api.example.com/users');
}
// 示例 2: POST 请求,创建用户(请求体类型为 User,响应类型为 User)
async function createUser(userData: User): Promise<User | RequestError> {
return request<User, User>('https://api.example.com/users', {
method: 'POST',
data: userData, // 类型安全检查:userData 必须匹配 User 接口
});
}
// 调用示例
(async () => {
const users = await fetchUsers();
if ('message' in users) {
console.error('Error:', users.message); // 类型化错误处理
} else {
console.log('Users:', users); // users 是 User[] 类型
}
const newUser = await createUser({ id: 1, name: 'Alice' });
if ('message' in newUser) {
console.error('Error:', newUser.message);
} else {
console.log('Created user:', newUser); // newUser 是 User 类型
}
})();
优势总结
- 类型安全:泛型 $T$ 和 $D$ 确保请求和响应数据在编译时检查,减少运行时错误。
- 可扩展性:通过高级泛型(如条件类型),轻松支持更多 HTTP 方法或自定义逻辑。
- 错误处理统一:
RequestError接口使错误信息结构化,便于调试。 - 代码重用:封装后,可在整个项目中复用,提升开发效率。
此实现基于 TypeScript 4.x+,确保真实可靠。在实际项目中,建议结合验证库(如 Zod)增强运行时类型安全。
更多推荐


所有评论(0)