关联泛型 (Conditional Generics)
关联泛型(也称为条件泛型)是 TypeScript 中一种强大的类型系统特性,它允许你根据某些条件来推导或选择类型。通过使用条件类型和泛型参数的组合,你可以创建出高度灵活且类型安全的代码。
1. 基本概念
关联泛型的核心在于条件类型(T extends U ? X : Y),它可以根据类型 T 是否扩展自类型 U 来选择不同的结果类型 X 或 Y。结合泛型参数,这种条件判断可以在定义函数、接口或类时动态地影响其行为。
2. 语法
type ConditionalType<T> = T extends U ? X : Y;T是一个泛型参数。U是一个用于比较的类型。X和Y是基于条件的结果类型。
3. 示例解析
示例 1: 基本条件类型
type IsString<T> = T extends string ? true : false;
console.log(IsString<string>); // 输出: true
console.log(IsString<number>); // 输出: false在这个例子中,IsString 根据传入的泛型参数是否为 string 类型返回 true 或 false。
示例 2: 结合泛型的条件类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result1 = UnwrapPromise<Promise<string>>; // string
type Result2 = UnwrapPromise<number>; // number在这个例子中,UnwrapPromise 可以从 Promise 中提取出包裹的类型,如果传入的不是 Promise 类型,则直接返回原类型。
示例 3: 使用多个条件
type TypeCheck<T> = T extends string
? "It's a string"
: T extends number
? "It's a number"
: "Unknown type";
console.log(TypeCheck<string>); // 输出: "It's a string"
console.log(TypeCheck<number>); // 输出: "It's a number"
console.log(TypeCheck<boolean>); // 输出: "Unknown type"在这个例子中,TypeCheck 根据传入的泛型参数选择不同的字符串字面量类型。
示例 4: 提取指定类型
type User = {
id: number;
name: string;
email: string;
address?: string;
};
type MyPick<T, K extends keyof T> = {
[key in K]: T[key];
};
type newUser = MyPick<User, "id" | "name">; // { id: number; name: string }
const user: newUser = {
id: 1,
name: "John Doe",
};这个例子中,MyPick 从 User 中提取出指定的键,并返回一个新类型。
4. 内置工具类型中的条件泛型
TypeScript 提供了一些常用的内置工具类型,这些工具类型内部使用了条件泛型:
Exclude<T, U>:从T中排除所有可以赋值给U的类型。Extract<T, U>:从T中提取出所有可以赋值给U的类型。NonNullable<T>:从T中排除null和undefined。ReturnType<T>:获取函数类型的返回值类型。InstanceType<T>:获取构造函数类型的实例类型。
示例:使用内置工具类型
type OnlyStrings = Exclude<string | number | boolean, number | boolean>; // string
type JustNumbers = Extract<string | number | boolean, number>; // number
type NotNullish = NonNullable<string | null | undefined>; // string
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType<typeof add>; // number
class MyClass {}
type MyClassInstance = InstanceType<typeof MyClass>; // MyClass5. 高级用法
5.1 分配条件类型
当条件类型作用于联合类型时,TypeScript 会自动将联合类型分配到条件类型中,分别处理每个成员。
type Flatten<T> = T extends any[] ? T[number] : T;
type Result1 = Flatten<string[]>; // string
type Result2 = Flatten<number>; // number
type Result3 = Flatten<string | string[]>; // string | string在这个例子中,Flatten 将数组类型转换为其元素类型,而对于非数组类型则保持不变。
5.2 推断类型
在条件类型中使用 infer 关键字可以从类型中推断出子类型。
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type FuncReturn1 = GetReturnType<() => string>; // string
type FuncReturn2 = GetReturnType<(a: number) => boolean>; // boolean在这个例子中,GetReturnType 可以从函数签名中提取出返回值类型。
6. 注意事项
复杂性管理:虽然条件泛型非常强大,但在实际开发中应尽量保持类型定义的简洁性和可读性,避免过度复杂的嵌套条件。
性能考虑:复杂的条件类型可能会增加编译时间,对于大型项目应权衡其利弊。
类型安全:确保条件逻辑不会破坏类型的安全性,特别是在处理联合类型和交叉类型时。
总结
关联泛型是 TypeScript 中一个非常重要的特性,它使得你可以根据条件动态地推导或选择类型。通过合理使用条件类型和泛型参数的组合,你可以编写出更加灵活且类型安全的代码。