TypeScript泛型在API SDK中的核心作用
TypeScript泛型是构建类型安全API SDK的核心工具。在前端开发中,API请求的请求参数和响应数据类型往往各不相同,泛型允许在保持类型检查的前提下实现通用的请求封装。泛型约束和条件类型进一步增强了类型系统的表达力,能够精确描述API请求与响应之间的类型映射关系,避免运行时类型错误。
类型安全的API SDK设计目标:编译时捕获参数类型错误,避免传入错误的请求参数;自动推断响应数据类型,减少手动类型断言;支持RESTful风格的路径参数类型推断;对不同HTTP方法提供不同的参数约束。
基础泛型请求封装
使用泛型封装fetch请求,实现请求和响应的类型安全:
// 基础请求参数类型
interface RequestOptions {
headers?: Record<string, string>;
timeout?: number;
signal?: AbortSignal;
}
// API响应包装类型
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// 泛型请求函数
async function request<T>(
url: string,
options: RequestInit & RequestOptions = {}
): Promise<ApiResponse<T>> {
const { headers, timeout = 10000, signal, ...init } = options;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...init,
headers: { 'Content-Type': 'application/json', ...headers },
signal: signal ?? controller.signal,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json() as ApiResponse<T>;
} finally {
clearTimeout(timeoutId);
}
}
// 使用示例:类型自动推断
interface User {
id: number;
name: string;
email: string;
}
// T 被推断为 User
const result = await request<User>('/api/users/1');
// result.data 的类型为 User,IDE自动补全
console.log(result.data.name); // 类型安全访问
泛型约束与条件类型实现RESTful路径推断
RESTful API的路径参数类型可以通过泛型约束和条件类型实现编译时推断,避免手动拼接字符串时出现参数遗漏或类型错误:
// 路径参数提取类型
type ExtractParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractParams<Rest>]: string | number }
: T extends `${string}:${infer Param}`
? { [K in Param]: string | number }
: {};
// 条件类型:检查路径是否包含参数
type HasParams<T extends string> = T extends `${string}:${string}` ? true : false;
// 路径替换函数(带类型约束)
function buildPath<T extends string>(
path: T,
...args: HasParams<T> extends true
? [ExtractParams<T>]
: []
): string {
if (args.length === 0) return path;
const params = args[0] as Record<string, string | number>;
return path.replace(/:(\w+)/g, (_, key) => String(params[key] ?? ''));
}
// 使用示例
// 无参数路径
const path1 = buildPath('/api/users');
// 有参数路径,类型系统强制要求传入参数对象
const path2 = buildPath('/api/users/:userId/posts/:postId', {
userId: 123,
postId: 456,
});
// 编译错误:缺少参数
// buildPath('/api/users/:userId'); // Error: Argument of type '[]' is not assignable
HTTP方法约束与请求体类型映射
不同HTTP方法对请求参数的约束不同,GET请求不需要body,POST/PUT请求需要body。通过条件类型和泛型约束实现方法级别的类型安全:
// HTTP方法类型
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
// 请求配置:根据方法类型条件性地要求body
type RequestConfig<M extends HttpMethod, B = unknown> =
M extends 'GET' | 'DELETE'
? { method: M; params?: Record<string, unknown> }
: { method: M; body: B; params?: Record<string, unknown> };
// API客户端类
class ApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
// 泛型方法:R为响应类型,B为请求体类型
async call<M extends HttpMethod, R, B = unknown>(
method: M,
path: string,
config: RequestConfig<M, B>
): Promise<ApiResponse<R>> {
const fullPath = this.baseUrl + path;
const init: RequestInit = { method };
if ('body' in config && config.body) {
init.body = JSON.stringify(config.body);
}
const url = new URL(fullPath);
if (config.params) {
Object.entries(config.params).forEach(([k, v]) =>
url.searchParams.set(k, String(v))
);
}
return request<R>(url.toString(), init);
}
}
// 使用示例
const client = new ApiClient('https://api.example.com');
// GET请求:不需要body
const userRes = await client.call('GET', '/users/1', { method: 'GET' });
// 带类型参数的调用
interface CreateUserDTO { name: string; email: string }
interface UserDTO { id: number; name: string; email: string }
const created = await client.call<'POST', UserDTO, CreateUserDTO>(
'POST', '/users',
{ method: 'POST', body: { name: 'test', email: 'test@test.com' } }
);
// created.data 类型为 UserDTO
// body 必须匹配 CreateUserDTO 类型,否则编译错误
映射类型生成API端点定义
利用映射类型和keyof操作符,可以从API定义对象自动生成类型安全的请求方法:
// API端点定义
interface UserApi {
getUser: {
method: 'GET';
path: '/users/:id';
params: { id: number };
response: User;
};
createUser: {
method: 'POST';
path: '/users';
body: { name: string; email: string };
response: User;
};
updateUser: {
method: 'PUT';
path: '/users/:id';
params: { id: number };
body: { name?: string; email?: string };
response: User;
};
deleteUser: {
method: 'DELETE';
path: '/users/:id';
params: { id: number };
response: void;
};
}
// 从端点定义生成类型安全的API方法
type ApiMethods<T extends Record<string, any>> = {
[K in keyof T]: T[K]['method'] extends 'GET' | 'DELETE'
? (params: T[K]['params']) => Promise<T[K]['response']>
: (params: T[K]['params'], body: T[K]['body']) => Promise<T[K]['response']>
};
// 实现类型安全的API层
function createApi<T extends Record<string, any>>(
client: ApiClient,
endpoints: T
): ApiMethods<T> {
const api = {} as ApiMethods<T>;
for (const key of Object.keys(endpoints) as (keyof T)[]) {
const ep = endpoints[key] as any;
(api as any)[key] = async (...args: any[]) => {
const path = buildPath(ep.path, args[0] || {});
return client.call(ep.method, path, {
method: ep.method,
body: args[1],
}).then(res => res.data);
};
}
return api;
}
// 使用示例:完全类型安全
const userApi = createApi(client, {
getUser: { method: 'GET', path: '/users/:id' },
createUser: { method: 'POST', path: '/users' },
updateUser: { method: 'PUT', path: '/users/:id' },
deleteUser: { method: 'DELETE', path: '/users/:id' },
} satisfies UserApi);
// 编译时类型检查
const user = await userApi.getUser({ id: 1 }); // 正确
await userApi.createUser({ id: 0 }, { name: 'a', email: 'a@a.com' }); // 正确
// userApi.getUser({ id: '1' }); // 编译错误:id应为number
条件类型实现API响应数据提取
在API SDK中,不同接口返回的数据结构可能嵌套层级不同。条件类型可以精确提取嵌套类型:
// 深层属性提取
type DeepPick<T, P extends string> =
P extends `${infer K}.${infer Rest}`
? K extends keyof T
? DeepPick<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never;
// 分页响应类型
interface PaginatedResponse<T> {
code: number;
data: {
list: T[];
total: number;
page: number;
pageSize: number;
};
}
// 条件类型:判断是否为分页响应
type UnwrapResponse<T> =
T extends PaginatedResponse<infer U>
? U[]
: T extends ApiResponse<infer U>
? U
: T;
// API方法返回类型自动推断
async function getList<T>(url: string): Promise<UnwrapResponse<PaginatedResponse<T>>> {
const res = await fetch(url);
const json: PaginatedResponse<T> = await res.json();
return json.data.list; // 返回 T[] 类型
}
interface Article {
id: number;
title: string;
}
const articles = await getList<Article>('/api/articles');
// articles 类型自动推断为 Article[]
articles.forEach(a => console.log(a.title)); // 类型安全
TypeScript泛型约束和条件类型在API SDK类型安全设计中发挥着关键作用。通过泛型参数传递请求和响应类型,结合条件类型实现HTTP方法级别的参数约束,再利用映射类型从接口定义自动生成类型安全的请求方法,可以在编译时捕获绝大部分API调用相关的类型错误。这种设计在前端工程化实践中减少了运行时调试成本,提升了代码可维护性。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-fan-xing-yue-shu-yu-tiao-jian-lei-xing-zai/