TypeScript泛型在API层设计中的核心作用
TypeScript实战中,泛型是实现类型安全代码复用的关键工具。前端开发场景下,API请求层的类型安全直接影响运行时可靠性。通过泛型约束和条件类型,可以在编译阶段捕获接口字段不匹配、返回值类型错误等问题,避免运行时才发现的Bug。
泛型基础与约束机制
泛型允许在定义函数、接口或类时使用类型参数,调用时再指定具体类型。泛型约束通过extends关键字限制类型参数的范围:
// 基础泛型函数
function getProperty(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'Alice', age: 30, email: 'a@test.com' };
const name = getProperty(user, 'name'); // 类型: string
const age = getProperty(user, 'age'); // 类型: number
// 泛型约束: 限制T必须有id字段
interface HasId {
id: number;
}
function findById(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
// 泛型默认类型
interface ApiResponse {
code: number;
message: string;
data: T;
}
// 不指定T时,data类型为unknown
const res: ApiResponse = { code: 200, message: 'ok', data: 'anything' };
条件类型与类型推断
条件类型根据输入类型动态选择输出类型,语法类似三元表达式。条件类型是构建高级类型工具的核心:
// 条件类型基本语法: T extends U ? X : Y
type IsString = T extends string ? true : false;
type A = IsString; // true
type B = IsString; // false
// infer关键字: 在条件类型中提取类型
type UnpackPromise = T extends Promise ? U : T;
type Result = UnpackPromise>; // string
// 提取数组元素类型
type ElementOf = T extends (infer E)[] ? E : never;
type Item = ElementOf; // number
// 提取函数返回值类型
type ReturnOf = T extends (...args: any[]) => infer R ? R : never;
type R = ReturnOf<() => boolean>; // boolean
映射类型与模板字面量类型
映射类型能批量转换已有类型的属性,模板字面量类型能在类型层面操作字符串:
// Partial: 所有属性变为可选
type MyPartial = {
[K in keyof T]?: T[K];
};
// Required: 所有属性变为必选
type MyRequired = {
[K in keyof T]-?: T[K];
};
// Readonly: 所有属性变为只读
type MyReadonly = {
readonly [K in keyof T]: T[K];
};
// Pick: 从T中选取部分属性
type MyPick = {
[P in K]: T[P];
};
// 模板字面量类型
type EventName = `on${Capitalize}`;
// 匹配: onClick, onChange, onKeyUp...
// 路由参数提取
type ExtractParams =
T extends `${string}:${infer P}/${infer Rest}`
? { [K in P]: string } & ExtractParams
: T extends `${string}:${infer P}`
? { [K in P]: string }
: {};
type Params = ExtractParams<'/users/:userId/posts/:postId'>;
// 结果: { userId: string; postId: string }
类型安全API请求层实现
结合泛型约束和条件类型,构建一个类型安全的API请求封装:
// API方法定义
interface ApiMethods {
getUser: (id: number) => Promise<{ name: string; age: number }>;
getList: (params: { page: number; size: number }) => Promise;
update: (data: { id: number; name: string }) => Promise;
}
// 类型安全的请求函数
type ApiMethodNames = keyof ApiMethods;
type ApiParams =
ApiMethods[M] extends (params: infer P) => any ? P : never;
type ApiResult =
ApiMethods[M] extends (...args: any) => Promise ? R : never;
class ApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
async request(
method: M,
params: ApiParams
): Promise> {
const url = `${this.baseUrl}/${method}`;
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
});
const data = await response.json();
return data as ApiResult;
}
}
// 使用时完全类型安全
const client = new ApiClient('https://api.example.com');
// 正确调用: 参数和返回值都有类型检查
const user = await client.request('getUser', { id: 1 });
console.log(user.name); // OK: string类型
// console.log(user.email); // Error: Property 'email' does not exist
// 错误调用: 编译时报错
// await client.request('getUser', { name: 'test' }); // Error: 缺少id
// await client.request('getList', { page: 1 }); // Error: 缺少size
运行时类型校验与类型守卫
TypeScript类型只在编译时存在,运行时需要通过类型守卫或校验库保障数据安全:
// 自定义类型守卫
function isUser(obj: unknown): obj is { name: string; age: number } {
return (
typeof obj === 'object' &&
obj !== null &&
'name' in obj &&
typeof (obj as any).name === 'string' &&
'age' in obj &&
typeof (obj as any).age === 'number'
);
}
// 使用zod进行运行时校验
import { z } from 'zod';
const UserSchema = z.object({
name: z.string(),
age: z.number().int().positive(),
email: z.string().email().optional(),
});
type User = z.infer;
async function fetchUser(id: number): Promise {
const res = await fetch(`/api/users/${id}`);
const raw = await res.json();
return UserSchema.parse(raw); // 运行时校验,失败抛出ZodError
}
zod的z.infer能从Schema定义自动推导TypeScript类型,实现运行时校验和编译时类型的统一。前端工程化中,API层的类型安全应覆盖请求参数、响应数据和错误处理三个维度。通过泛型约束确保参数类型正确,通过条件类型实现返回值类型自动推导,通过运行时校验兜底不可信的外部数据。组件库设计中也应充分利用泛型,如Table组件的泛型定义能让列配置与数据类型保持同步。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-fan-xing-yue-shu-yu-tiao-jian-lei-xing-shi-zhan/