上篇文章讲述了泛型的基础用法,下面是关于 TypeScript 泛型的一些高级知识点,简单介绍一下。
1. 条件类型中的泛型约束
条件类型 (T extends U ? X : Y) 是 TypeScript 的一种高级特性,它根据类型的条件返回不同的结果。这种约束在泛型中非常实用,可以根据 传入的类型 动态生成 结果类型。
使用场景:用于校验参数类型,例如动态生成函数响应类型,或在表单校验中提供不同的返回值提示。
🌰
TypeScript
type IsString<T> = T extends string ? 'yes' : 'no';
type Result1 = IsString<number>; // "no"
type Result2 = IsString<string>; // "yes"
例子中,如果 T 是 string 类型,则 IsString<T> 返回 "yes",否则返回 "no"。
2. 分布式条件类型
当类型参数是联合类型时,条件类型会对联合类型的每个成员逐一应用条件,这称为分布式条件类型。
使用场景:常用于过滤联合类型的成员。例如,在权限校验或数据解析中,排除不符合条件的类型成员。
🌰
TypeScript
type ExcludeStringOrNumber<T> = T extends string | number ? never : T;
type Result = ExcludeStringOrNumber<string | boolean | number>; // boolean
例子中,T 被分解为 string、boolean 和 number,每个成员分别应用条件类型,然后合并结果。此处,string 和 number 被过滤掉,最终返回 boolean 类型。这种特性允许对联合类型的每个成员单独处理。
3. 递归泛型类型
TypeScript 支持通过递归来定义类型,在处理树形结构或嵌套数据时非常有用。
使用场景:用于定义无限嵌套的数组或对象类型,例如在 JSON 数据解析中动态定义嵌套数据的类型。
🌰
TypeScript
type Nested<T> = T | Nested<T>[];
const data: Nested<string> = ['hello', ['world', ['!']]]; // 嵌套字符串数组
递归泛型 Nested<T> 可以处理无限层次的嵌套数据,确保每层嵌套的元素类型一致。
4. 分布式条件类型
在 TypeScript 中,条件类型会在联合类型上自动分发。具体来说,当条件类型的左侧是一个泛型参数(如 T),且 T 是联合类型时,TypeScript 会将每个联合成员单独放在条件类型中进行计算,并最终合并结果。这种特性被称为 分布式条件类型。
分布式条件类型是一种对联合类型的每个组成部分单独应用条件逻辑的特性。在处理复杂类型推断和条件判断时非常有用。
当 T 是一个联合类型(如 string | number)时,TypeScript 会对联合类型的每个成员分别应用条件类型逻辑,而不是直接应用到整个联合类型上。
🌰:定义一个类型
TypeScript
type MyType<T> = T extends string ? '字符串类型' : '非字符串类型';
type Result = MyType<string | number>;
T 变成了联合类型 string | number,所以 MyType<T> 会自动分解为:
TypeScript
MyType<string> | MyType<number>
然后对每个成员单独应用条件判断:
MyType<string>:string 满足 T extends string 的条件,返回 '字符串类型'。
MyType<number>:number 不满足 T extends string 的条件,返回 '非字符串类型'。
最终结果是:
TypeScript
type Result = '字符串类型' | '非字符串类型';
5. Mapped Types 与泛型组合
映射类型是 TypeScript 的特性,允许对一个对象类型的每个属性进行操作。这种操作可以是增加属性修饰符(如 只读、可选)、更改属性值类型、重命名等。结合泛型,映射类型可以动态地根据输入类型生成新类型。
可读:🌰
TypeScript
type ReadonlyType<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
name: string;
age: number;
}
type ReadonlyUser = ReadonlyType<User>; // 所有属性变为只读 { readonly name: string; readonly age: number; }
keyof T 表示类型 T 的所有键组合的联合类型,[P in keyof T] 是对 T 类型的每个键 P 进行一次遍历,这种遍历称为"映射类型",将对象的每个属性逐一映射到新类型中。
T[P] 表示 T 类型中键 P 对应的值的类型,也就是说,对于每个 P,保持其类型不变。
ReadonlyType<T> 会将对象的每个属性设置为只读。
可选:🌰
TypeScript
type OptionalType<T> = {
[P in keyof T]?: T[P];
};
type OptionalUser = OptionalType<User>;
6. 键值联合 (Key Remapping)
TypeScript 4.1 引入的键值联合特性,可以在映射类型中重映射键名。
使用场景:应用于从外部 API 获取数据并需要重命名字段名的场景。也可用于前端动态合成自定义字段。
🌰
TypeScript
type MappedType<T> = {
[K in keyof T as `new_${K & string}`]: T[K];
};
interface User {
name: string;
age: number;
}
type NewUser = MappedType<User>; // { new_name: string; new_age: number }
这里,键值映射允许为 keyof T 的每个键加上 new_ 前缀,这种方法在重命名或动态合成属性名时非常有用。
7. infer 关键字
在 TypeScript 中,infer 常用在条件类型中,用于在类型推导过程中"推测"类型的一部分。它允许我们在复杂类型中提取出一部分类型信息并将其存储到一个局部类型变量中,可以对其进一步操作。这个特性在处理函数的参数类型、返回类型,或者对复杂数据结构进行类型提取时非常有用。
7.1 基本原理
TypeScript
// 基本格式
T extends SomeType<infer U> ? X : Y
-
infer U:如果 T 满足 SomeType 的条件,那么 U 就是从 SomeType 中提取的类型。U 类型是一个局部变量,可以在条件类型的 true 分支(即 X)中使用。
-
X 和 Y:是条件类型的返回值,根据 T 是否符合条件决定使用哪个分支。
🌰:假设有一个函数类型 (x: number) => string,希望提取它的返回类型,可以使用 infer 来实现。
TypeScript
type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never;
type MyFunction = (x: number) => string;
type Result = ReturnTypeOf<MyFunction>; // Result 被推导为 string
在上面的例子中,T 是传入的类型,如果 T 是一个函数类型(符合 (...args: any[]) => infer R 的形式),那么 提取 R 为返回类型。在 Result 中,返回类型为 string。
7.2 常见应用场景
1、提取函数的参数类型,生成一个包含所有参数类型的元组。
🌰
TypeScript
type ParametersOf<T> = T extends (...args: infer P) => any ? P : never;
function greet(name: string, age: number): string {
return `Hello ${name}, you are ${age} years old.`;
}
type GreetParameters = ParametersOf<typeof greet>; // [string, number]
ParametersOf<T>:如果 T 是一个函数类型,则返回该函数的参数类型组成的元组 P。
2、提取 Promise 的返回值类型
🌰
TypeScript
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result1 = UnwrapPromise<Promise<number>>; // number
type Result2 = UnwrapPromise<number>; // number(非 Promise 类型,直接返回 T)
UnwrapPromise<T>:检查 T 是否为 Promise<infer U> 类型,如果是,则返回 U。
3、从嵌套结构中提取类型
在复杂的数据结构(例如嵌套数组、对象等)中,可以使用 infer 逐层提取出某个目标类型。
🌰
TypeScript
type ElementType<T> = T extends (infer U)[] ? U : T;
type Result1 = ElementType<number[]>; // number
type Result2 = ElementType<string[][]>; // string
type Result3 = ElementType<boolean>; // boolean
ElementType<T>:检查 T 是否为数组类型 (infer U)[],如果是,则返回数组元素类型 U。
总结
以上是一些关于泛型复杂的知识点,包括条件类型、递归泛型和键值联合等特性,为 TypeScript 提供了丰富的类型控制能力,使代码更具复用性。简单介绍一下,大家感兴趣的话可以深入了解一下。