TypeScript 泛型高级用法:从入门到实战
TypeScript 泛型高级用法:从入门到实战
引言
在 TypeScript 的类型系统中,泛型(Generics)是最强大也最容易被误解的特性之一。许多开发者只停留在基础用法层面,却不知道泛型能够解决多少实际问题。本文将深入探讨 TypeScript 泛型的高级用法,帮助你写出更类型安全、更灵活的代码。
什么是泛型?
泛型的本质是"类型参数化"。就像函数可以接受参数一样,泛型允许我们给类型也传递参数。这样,同一个类型定义可以适用于多种不同的具体类型。
最基础的泛型示例:
function identity<T>(arg: T): T {
return arg;
}
// 使用
const result1 = identity<string>("hello"); // string
const result2 = identity<number>(42); // number
但这只是冰山一角。让我们深入探索泛型的高级用法。
一、泛型约束(Generic Constraints)
在实际开发中,我们经常需要限制泛型参数的范围。这时就需要用到泛型约束。
基础约束
使用 INLINE_CODE_0 关键字可以约束泛型必须满足某个接口或类型:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
// ✅ 正确
loggingIdentity("hello"); // string 有 length
loggingIdentity([1, 2, 3]); // array 有 length
loggingIdentity({ length: 10 }); // 对象有 length 属性
// ❌ 错误
loggingIdentity(42); // number 没有 length
多约束条件
TypeScript 4.0+ 支持更复杂的约束组合:
interface Identifiable {
id: string | number;
}
interface Timestamped {
createdAt: Date;
}
// 同时满足多个约束
function processEntity<T extends Identifiable & Timestamped>(entity: T): T {
console.log(`Processing entity ${entity.id} created at ${entity.createdAt}`);
return entity;
}
const user = {
id: "user-123",
createdAt: new Date(),
name: "Alice"
};
processEntity(user); // ✅ 正确
二、条件类型与泛型
条件类型是 TypeScript 类型系统的"三元运算符",与泛型结合使用可以实现强大的类型推导。
基础条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false
实用工具类型实现
TypeScript 内置的许多工具类型都是基于条件类型实现的。让我们看看如何自己实现:
// 实现 MyExclude
type MyExclude<T, U> = T extends U ? never : T;
type Result = MyExclude<"a" | "b" | "c", "a">; // "b" | "c"
// 实现 MyPick
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface User {
id: number;
name: string;
email: string;
age: number;
}
type BasicUser = MyPick<User, "id" | "name">;
// 等价于 { id: number; name: string; }
// 实现 MyOmit
type MyOmit<T, K extends keyof T> = MyPick<T, MyExclude<keyof T, K>>;
type UserWithoutAge = MyOmit<User, "age">;
// 等价于 { id: number; name: string; email: string; }
三、映射类型(Mapped Types)
映射类型允许我们基于已有类型创建新类型,通过遍历类型的属性键来实现。
基础映射类型
// 将所有属性变为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 将所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 将所有属性变为必填
type Required<T> = {
[P in keyof T]-?: T[P];
};
高级映射类型
结合条件类型和映射类型,可以实现更复杂的转换:
// 将对象的字符串属性变为可选,其他属性保持不变
type PartialStringProps<T> = {
[P in keyof T as T[P] extends string ? P : never]?: T[P];
} & {
[P in keyof T as T[P] extends string ? never : P]: T[P];
};
interface Product {
id: number;
name: string;
description: string;
price: number;
inStock: boolean;
}
type PartialStringProduct = PartialStringProps<Product>;
// 结果:{ id: number; price: number; inStock: boolean; name?: string; description?: string; }
四、模板字面量类型
TypeScript 4.1 引入的模板字面量类型可以与泛型结合,实现动态类型生成。
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Resource = "users" | "posts" | "comments";
// 生成所有可能的 API 端点类型
type ApiEndpoint = `${HttpMethod} /${Resource}`;
// 结果:"GET /users" | "GET /posts" | "GET /comments" | "POST /users" | ...
// 生成事件处理器类型
type EventName<T extends string> = `${T}Changed` | `${T}Updated`;
type UserEvents = EventName<"user">;
// 结果:"userChanged" | "userUpdated"
五、实战应用:API 响应类型
在实际项目中,泛型可以帮助我们构建类型安全的 API 层。
// 定义通用 API 响应结构
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: {
code: string;
message: string;
};
pagination?: {
page: number;
pageSize: number;
total: number;
};
}
// 定义用户类型
interface User {
id: number;
name: string;
email: string;
}
// 使用泛型定义具体的 API 响应
type UserResponse = ApiResponse<User>;
type UserListResponse = ApiResponse<User[]>;
// 泛型 API 客户端
class ApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
async get<T>(endpoint: string): Promise<ApiResponse<T>> {
const response = await fetch(`${this.baseUrl}${endpoint}`);
return response.json();
}
async post<T>(endpoint: string, data: unknown): Promise<ApiResponse<T>> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
return response.json();
}
}
// 使用示例
const api = new ApiClient("https://api.example.com");
// 获取单个用户 - 类型自动推断
const userResult = await api.get<User>("/users/1");
if (userResult.success && userResult.data) {
console.log(userResult.data.name); // 类型安全
}
// 获取用户列表
const usersResult = await api.get<User[]>("/users");
if (usersResult.success && usersResult.data) {
usersResult.data.forEach(user => {
console.log(user.email); // 类型安全
});
}
六、实战应用:状态管理模式
泛型在状态管理中同样大有用武之地:
// 定义通用 Action 类型
interface Action<T extends string, P = undefined> {
type: T;
payload?: P;
}
// 定义用户相关 Actions
type UserAction =
| Action<"USER_LOGIN", { userId: number; token: string }>
| Action<"USER_LOGOUT">
| Action<"USER_UPDATE", Partial<User>>;
// 定义通用 Reducer 类型
type Reducer<State, Actions extends Action<string, any>> = (
state: State,
action: Actions
) => State;
// 用户状态
interface UserState {
isLoggedIn: boolean;
user: User | null;
token: string | null;
}
// 用户 Reducer
const userReducer: Reducer<UserState, UserAction> = (state, action) => {
switch (action.type) {
case "USER_LOGIN":
return {
...state,
isLoggedIn: true,
user: { id: action.payload.userId, name: "", email: "" },
token: action.payload.token,
};
case "USER_LOGOUT":
return {
...state,
isLoggedIn: false,
user: null,
token: null,
};
case "USER_UPDATE":
return {
...state,
user: state.user ? { ...state.user, ...action.payload } : null,
};
default:
return state;
}
};
七、高级技巧:类型推断优化
TypeScript 提供了多种方式来优化泛型的类型推断。
使用 infer 关键字
// 从 Promise 类型中提取内部类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>; // number
// 从函数类型中提取参数和返回类型
type FirstParameter<T extends (...args: any[]) => any> =
T extends (arg: infer P, ...args: any[]) => any ? P : never;
type ReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R ? R : never;
function greet(name: string): string {
return `Hello, ${name}`;
}
type Param = FirstParameter<typeof greet>; // string
type Return = ReturnType<typeof greet>; // string
泛型默认值
TypeScript 2.3+ 支持泛型默认值:
interface Container<T = string> {
value: T;
}
const strContainer: Container = { value: "hello" }; // Container<string>
const numContainer: Container<number> = { value: 42 }; // Container<number>
结语
TypeScript 泛型是构建类型安全应用的强大工具。从基础的类型参数化,到复杂的条件类型、映射类型,泛型提供了丰富的表达能力。掌握这些高级用法,可以让你:
- 减少重复代码:一套类型定义适配多种场景
- 提高类型安全:编译时捕获更多错误
- 增强代码可读性:类型即文档
- 提升开发体验:更好的智能提示和自动补全
记住,泛型不是炫技的工具,而是解决实际问题的利器。在实际项目中,应该根据需求选择合适的泛型复杂度,在类型安全和代码可读性之间找到平衡点。
开始在你的项目中使用这些泛型技巧吧,你会发现 TypeScript 的类型系统远比想象中更强大!