TypeScript 提供了一些实用类型工具方法(Utility Types),以方便进行常见的类型转换。这些类型工具方法在全局范围内都能使用。
Partial<Type>Readonly<Type>Record<Keys,Type>Pick<Type, Keys>Required<Type>Exclude<Type, ExcludedUnion>Extract<Type, Union>NonNullable<Type>Omit<Type, Keys>Parameters<Type>ConstructorParameters<Type>ReturnType<Type>InstanceType<Type>ThisParameterType<Type>OmitThisParameter<Type>ThisType<Type>
singsong: 这些方法在 TypeScript 中,也被称为
Mapped Types。
在介绍这些方法之前,首先得先掌握一些基础概念,以便更好地理解这些方法源码。
相关基础知识介绍
-
keyofkeyof操作符,也称为索引类型查询符。是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是所有键联合类型。其行为与Object.keys()方法类似。interface Person { name: string; age: number; location: string; } type K1 = keyof Person; // "name" | "age" | "location" type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ... type K3 = keyof { [x: string]: Person }; // string -
查询类型(Lookup Types)
语法上,类似于元素访问。而这里是类型查询
type P1 = Person['name']; // string type P2 = Person['name' | 'age']; // string | number type P3 = string['charAt']; // (pos: number) => string type P4 = string[]['push']; // (...items: string[]) => number type P5 = string[][0]; // string -
in操作符,表示如果指定属性在指定对象中,则会返回true。 在 Tyepscript 中常用于Mapped Types遍历联合类型所有键。interface Person { name: string; age: number; } type Partial<T> = { [P in keyof T]?: T[P]; //等效于 P in ("name" | "age") }; type PersonPartial = Partial<Person>; // 等同于 { name?: string; age?: number; }还可用于类型保护(Type Guards)
interface A { x: number; } interface B { y: string; } let q: A | B = ...; if ('x' in q) { // q: A } else { // q: B }
-
在使用泛型时,由于无法确定类型,随意地操作其属性或方法,可能会导致编译报错:
function loggingIdentity<T>(arg: T): T { console.log(arg.length); return arg; } // index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.上述实例中,泛型
T不一定包含属性length,所以编译时报错。 这里可以对泛型T进行约束,泛型T必须包含length属性才能允许传入。这就是泛型约束。interface Lengthwise { length: number; } function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg; }上述实例中,
extends Lengthwise对泛型T进行约束,T 必须符合接口Lengthwise形状(即包含length)属性。 -
never类型表示永远不会发生的类型。主要有如下两种场景:
-
绝不会有返回值的函数
function infiniteLoop(): never { while (true) {} } -
总是抛出错误的函数
function error(message: string): never { throw new Error(message); }
❗️ 注意:
never只能赋值给另一个never,不能赋值给其他类型 -
-
条件类型(Conditional Types)
SomeType extends OtherType ? TrueType : FalseType;当
SomeType extends OtherType中的SomeType被赋值,就取TrueType,否则取FalseType。分发条件类型(Distributive conditional types)
如果
SomeType是一个 naked type parameter(裸类型参数),则该条件类型为分发条件类型。singsong: 什么是 naked type parameter(裸类型参数)。它表示没有被包裹的类型,如:Array、[T]、Promise 等都不是 naked type parameter(裸类型参数)
分发条件类型 主要用于拆分
extends左边部分的联合类型。如type DCType<T> = T extends U ? X : Y; // 如果 T 取值 A | B | C type DCType<A|B|C> = (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y);
-
infer表示在
extends条件类型 中待推断的类型变量。type ParamType<T> = T extends (param: infer P) => any ? P : T;如果
T能赋值给(param: infer P) => any,则结果是(param: infer P) => any类型中的参数 P,否则返回为 T。
Partial
将某个类型 Type 中的属性全部转换为可选属性(?)
源码
//github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1459
/**
* Make all properties in T optional
*/
https: type Partial<T> = {
[P in keyof T]?: T[P];
};
源码解读:首先通过 keyof 获取类型变量 T 的所有键的联合类型 keyof T,再使用 in 操作符遍历,将所有键变为可选 [p]?,并通过 Lookup Types 获取对应的类型值 T[P] 赋值给 [P]?: T[P]。
实例演示
interface Todo {
title: string;
description: string;
}
type TodoPartial = Partial<Todo>;
// 等同于
type TodoPartial = {
title?: string;
description?: string;
};
-
keyof Todo返回"title" | "description"; -
[P in keyof T]?: T[P]等同于[P in "title" | "description"]?: Todo[P]; 类似于for(let P in Todo){ TodoPartial[P]?: Todo[P] } -
TodoPartial等于{title?: string; description?: string;}。
Readonly
将某个类型 Type 中的属性全部转换为只读属性(readonly)。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1473
/**
* Make all properties in T readonly
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
源码解读:首先通过 keyof 获取类型变量 T 的所有键的联合类型 keyof T,再使用 in 操作符遍历,将所有键变为只读 readonly [p],并通过 Lookup Types 获取对应的类型值 T[P] 赋值给 readonly [P]: T[P]。
实例演示
interface Todo {
title: string;
description: string;
}
type TodoReadonly = Readonly<Todo>;
// 等同于
type TodoReadonly = {
readonly title: string;
readonly description: string;
};
-
keyof Todo返回"title" | "description"; -
readonly [P in keyof T]: T[P]等同于readonly [P in "title" | "description"]: Todo[P]; 类似于for(let P in Todo){ readonly TodoReadonly[P]: Todo[P] } -
TodoReadonly等于{ readonly title: string; readonly description: string; }
Record<Keys,Type>
将 Keys 所有的键转为了 Type 类型
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1487
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
源码解读:这里 keyof any 等同于 string | number | symbol,即索引类型所有合法取值。 K extends keyof any 等同于 K extends string | number | symbol,对 K 进行 泛型约束,即 K 只能是 string | number | symbol 其中任何一个。[P in K]: T; 使用 in 操作符遍历,将所有键都赋值 T。
实例演示
interface Todo {
title: string;
description: string;
}
type Pages = 'home' | 'about';
type TodoRecord = Record<Pages, Todo>;
// 等同于
type TodoRecord = {
home: {
title: string;
description: string;
};
about: {
title: string;
description: string;
};
};
-
Pages只能取值string | number | symbol; -
[P in Pages]: Todo等同于[P in "home" | "about"]: Todo; 类似于for(let P in Pages){ TodoRecord[P]: Todo } -
TodoRecord等于home: { title: string; description: string; } about: { title: string; description: string; }
Pick<Type, Keys>
从某个类型 Type 中,提取 Keys 中指定的所有键的子类型。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1480
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
源码解读:这里 K extends keyof T 主要对 K 进行 泛型约束,取值只能是 keyof T。 [P in K]: T; 使用 in 操作符遍历,并通过 Lookup Types 获取对应的类型值 T[P] 赋值给 [P]: T[P]
实例演示
interface Todo {
id: number;
title: string;
description: string;
}
type Keys = 'title' | 'description';
type TodoPick = Pick<Todo, Keys>;
// 等同于
type TodoPick = {
title: string;
description: string;
};
-
Keys只能取值"id" | title" | "description"; -
[P in Keys]: Todo[P]等同于[P in "title" | "description"]: Todo[P]; 类似于for(let P in Keys){ TodoPick[P]: Todo[P] } -
TodoPick等于{ title: string; description: string; }
Required<Type>
将某个类型 Type 中的属性全部转换为必选属性。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1466
/**
* Make all properties in T required
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};
源码解读:首先通过 keyof 获取类型变量 T 的所有键的联合类型 keyof T,再使用 in 操作符遍历,将所有键变为”必选“ [p]-?,并通过 Lookup Types 获取对应的类型值 T[P] 赋值给 [P]-?: T[P]。
singsong: 这里的
"-?"表示什么 🤔🤔。它表示删除可选符号?,即 必选。
实例演示
interface Todo {
title?: string;
description?: string;
}
type TodoRequired = Required<Todo>;
// 等同于
type TodoRequired = {
title: string;
description: string;
};
-
keyof Todo返回"title" | "description"; -
[P in keyof T]-?: T[P]等同于[P in "title" | "description"]-?: Todo[P]; 类似于for(let P in Todo){ TodoRequired[P]-?: Todo[P] } -
TodoRequired等于{ title: string; description: string; }
Exclude<Type, ExcludedUnion>
将 Type 类型中属于另一个的类型 ExcludedUnion 移除掉
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1494
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;
源码解读:如果 T 被赋值(T 符合 U 约束),就取 never(移除),否则取 T。
实例演示
type T = Exclude<'a' | 'b' | 'c', 'a' | 'b'>;
// 等同于
type T = 'a' extends 'a' | 'b' ? never : 'a' | 'b' extends 'a' | 'b' ? never : 'b' | 'c' extends 'a' | 'b' ? never : 'c'; // 分发条件类型
// 等同于
type T = 'c';
singsong: 当条件类型用于泛型时,如果
T是个联合类型,会进行分发条件类型(Distributive Conditional Types
Extract<Type, Union>
将 Type 类型中属于另一个的类型 Union 提取出构建成新的子类型。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1499
/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never;
源码解读:如果 T 被赋值(T 符合 U 约束),就取 T(提取),否则取 never(移除)。
实例演示
type T = Extract<'a' | 'b' | 'c', 'a' | 'b'>;
// 等同于
type T = 'a' extends 'a' | 'b' ? 'a' : never | 'b' extends 'a' | 'b' ? 'b' : never | 'c' extends 'a' | 'b' ? 'c' : never; // 分发条件类型
// 等同于
type T = 'a' | 'b';
singsong: 当条件类型用于泛型时,如果
T是个联合类型,会进行分发条件类型(Distributive Conditional Types
NonNullable<Type>
将 Type 类型中属于 null | undefined 移除。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1509
/**
* Exclude null and undefined from T
*/
type NonNullable<T> = T extends null | undefined ? never : T;
源码解读:如果 T 被赋值(T 符合 null | undefined 约束),就取 never(移除),否则取 T。
实例演示
type T = NonNullable<string | number | undefined>;
// 等同于
type T = string extends null | undefined
? never
: string | number extends null | undefined
? never
: number | undefined extends null | undefined
? never
: undefined; // 分发条件类型
// 等同于
type T = string | number;
Omit<Type, Keys>
从某个类型 Type 中,提取 Keys 中指定的所有键的子类型。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1504
/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
源码解读:这里 keyof any 等同于 string | number | symbol,即索引类型所有合法取值。 K extends keyof any 等同于 K extends string | number | symbol,对 K 进行 泛型约束,即 K 只能是 string | number | symbol 其中任何一个。Exclude<keyof T, K> 使用 Exclude 方法将 K 从 T 移除。Pick<T, Exclude<keyof T, K>> 使用 Pick 方法从 T 中提取 Exclude<keyof T, K>。关于 Exclude 与 Pick 请参考上述讲解。
实例演示
interface Todo {
id: number;
title: string;
description: string;
}
type Keys = 'title' | 'description';
type TodoOmit = Omit<Todo, Keys>;
// 等同于
type TodoOmit = {
id: number;
};
-
使用
Exclude方法,移除Keys:Exclude<Todo, 'title' | 'description'>; -
使用
Pick方法,从Todo中提取Exclude<Todo, 'title' | 'description'>:Pick<Todo, Exclude<Todo, 'title' | 'description'>>; -
TodoOmit等于{ id: number; }
Parameters<Type>
获取 Type 函数类型的参数类型。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1514
/**
* Obtain the parameters of a function type in a tuple
*/
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
源码解读:
T extends (...args: any) => any 这里主要对 T 进行泛型约束,只能取函数类型。
type paramType = Parameters<number>;
// ❌ Type 'number' does not satisfy the constraint '(...args: any) => any'.
T extends (...args: infer P) => any ? P : never 表示 T 如果符合 (...args: infer P) => any 约束,就返回 P,否则 never。infer P 用于表示 T 函数参数类型。因为不知道函数参数类型,需要使用 infer 声明一个新的类型变量 P 表示参数类型。
singsong:
infer只能用于extends的条件语句中。
实例演示
type add = (arg: { a: number; b: string }) => number;
type addParamType = Parameters<add>;
// 等同于
type addParamType = [
arg: {
a: number;
b: string;
}
];
关于 infer 更多
ConstructorParameters<Type>
获取 Type 构造函数类型的参数类型。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1519
/**
* Obtain the parameters of a constructor function type in a tuple
*/
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
源码解读:
T extends new (...args: any) => any 这里主要对 T 进行泛型约束,只能取构造函数类型。
type add = (arg: { a: number; b: string }) => number;
type constructorParametersType = ConstructorParameters<add>;
// ❌ Type '(arg: { a: number; b: string; }) => any' does not satisfy the constraint 'new (...args: any) => any'.
// ❌ Type '(arg: { a: number; b: string; }) => any' provides no match for the signature 'new (...args: any): any'.
T extends new (...args: infer P) => any ? P : never 表示 T 如果符合 new (...args: infer P) => any 约束,就返回 P,否则 never。infer P 用于表示 T 构造函数参数类型。因为不知道构造函数参数类型,需要使用 infer 声明一个新的类型变量 P 表示参数类型。
singsong:
infer只能用于extends的条件语句中。
实例演示
type Add = new (arg: { a: number; b: string }) => number;
type constructorParametersType = ConstructorParameters<Add>;
// 等同于
type constructorParametersType = [
arg: {
a: number;
b: string;
}
];
InstanceType<Type>
获取 Type 构造函数类型的返回类型。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1524
/**
* Obtain the return type of a constructor function type
*/
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
源码解读:
T extends new (...args: any) => any 这里主要对 T 进行泛型约束,只能取构造函数类型。
type add = (arg: { a: number; b: string }) => number;
type instanceType = InstanceType<add>;
// ❌ Type '(arg: { a: number; b: string; }) => any' does not satisfy the constraint 'new (...args: any) => any'.
// ❌ Type '(arg: { a: number; b: string; }) => any' provides no match for the signature 'new (...args: any): any'.
T extends new (...args: any) => infer R ? R : any 表示 T 如果符合 new (...args: any) => infer R 约束,就返回 R,否则 any。infer R 用于表示 T 构造函数类型的返回类型。因为不知道构造函数返回类型,需要使用 infer 声明一个新的类型变量 R 表示返回类型。
singsong:
infer只能用于extends的条件语句中。
实例演示
type Add = new (arg: { a: number; b: string }) => number;
type instanceType = InstanceType<Add>;
// 等同于
type instanceType = number;
ReturnType<Type>
获取 Type 函数类型的返回类型。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1514
/**
* Obtain the return type of a function type
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
源码解读:
T extends (...args: any) => any 这里主要对 T 进行泛型约束,只能取函数类型。
type returnType = ReturnType<number>;
// ❌ Type 'number' does not satisfy the constraint '(...args: any) => any'.
T extends (...args: any) => infer R ? R : any 表示 T 如果符合 (...args: any) => infer R 约束,就返回 R,否则 any。infer R 用于表示 T 函数类型的返回类型。因为不知道函数返回类型,需要使用 infer 声明一个新的类型变量 R 表示返回类型。
singsong:
infer只能用于extends的条件语句中。
实例演示
type fn = () => { a: number; b: string };
type returnType = ReturnType<fn>;
// 等同于
type returnType = {
a: number;
b: string;
};
ThisParameterType<Type>
获取 Type 函数类型的 this 参数类型,如果函数类型没有 this 参数,则返回 unknown。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L321
/**
* Extracts the type of the 'this' parameter of a function type, or 'unknown' if the function type has no 'this' parameter.
*/
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;
源码解读:
T extends (this: infer U, ...args: any[]) => any ? U : unknown 表示 T 如果符合 (this: infer U, ...args: any[]) => any 约束,就返回 U,否则 unknown。infer U 用于表示 U 函数类型的 this 参数类型。因为不知道函数类型的 this 参数类型,需要使用 infer 声明一个新的类型变量 U 表示返回类型。
singsong:
infer只能用于extends的条件语句中。
实例演示
function toHex(this: Number) {
return this.toString(16);
}
type thisParameterType = ThisParameterType<typeof toHex>; // typeof toHex 用于获取 `toHex` 函数类型
// 等同于
type thisParameterType = Number;
OmitThisParameter<Type>
删除 Type 函数类型的 this 参数类型。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L326
/**
* Removes the 'this' parameter from a function type.
*/
type OmitThisParameter<T> = unknown extends ThisParameterType<T>
? T
: T extends (...args: infer A) => infer R
? (...args: A) => R
: T;
源码解读:
unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T 表示 T 函数类型没有 this 参数类型(unknown extends ThisParameterType<T> 为 true),则直接返回 T。否则 T extends (...args: infer A) => infer R ? (...args: A) => R : T 表示如果 T 是个函数类型,则创建一个新的没有 this 参数类型的函数类型((...args: A) => R);否则直接返回 T。
singsong:
infer只能用于extends的条件语句中。
实例演示
function toHex(this: Number) {
return this.toString(16);
}
type omitThisParameterType = OmitThisParameter<typeof toHex>; // typeof toHex 用于获取 `toHex` 函数类型
// 等同于
type omitThisParameterType = () => string;
ThisType<Type>
控制 this 类型,该方法只在 --noImplicitThis 选项开启条件下才有效。
源码
// https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1534
/**
* Marker for contextual 'this' type
*/
interface ThisType<T> {}
源码解读:
一个空接口,它不返回任何转换过的类型,而是作为对象字面量上下文 this 标识。如果要使用这个类型,需要启用配置 -noImplicitThis 。
实例演示
type Person = {
name: string;
age: number;
};
type Student = {
context: ThisType<Person> & { id: number };
};
// 等同于
type Student = {
context: {
name: string;
age: number;
} & { id: number };
};
总结
本文以实例形式详细地解读了 TypeScript 中 Utility Types。只有了解 Utility Types 实现细节,才能愉快地使用和定制 Utility Types 。。。。😁 😁 😁