Parameters
Parameters 用于提取函数类型的参数类型。
源码是这样的:
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
1. 参数说明
T
:要从中提取参数类型的函数类型。
2. 条件类型 T extends (...args: infer P) => any
这个条件类型用于检查
T
是否是一个函数类型。(...args: infer P) => any
是一个函数签名,其中...args: infer P
表示函数可以接受任意数量和类型的参数,infer P
用于推断函数的参数类型。
3. 分支 ? P : never
如果
T
是一个函数类型,并且成功推断出参数类型P
,则返回P
。如果
T
不是一个函数类型,或者无法推断出参数类型,则返回never
。
类型参数 T 为待处理的类型,通过 extends 约束为函数,参数和返回值任意。
通过 extends 匹配一个模式类型,提取参数的类型到 infer 声明的局部变量 P 中返回。
这样就实现了函数参数类型的提取:
function add(a: number, b: number): number { return a + b; } type AddParameters = Parameters<typeof add>; // [number, number]
ReturnType
ReturnType 用于提取函数类型的返回值类型。
源码是这样的:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
1. 参数说明
T
:要从中提取返回类型的函数类型。
2. 条件类型 T extends (...args: any) => infer R
这个条件类型用于检查
T
是否是一个函数类型。(...args: any) => infer R
是一个函数签名,其中...args: any
表示函数可以接受任意数量和类型的参数,infer R
用于推断函数的返回类型。
3. 分支 ? R : any
如果
T
是一个函数类型,并且成功推断出返回类型R
,则返回R
。如果
T
不是一个函数类型,或者无法推断出返回类型,则返回any
。
类型参数 T 为待处理的类型,通过 extends 约束为函数类型,参数和返回值任意。
用 T 匹配一个模式类型,提取返回值的类型到 infer 声明的局部变量 R 里返回。
这样就实现了函数返回值类型的提取:
function add(a: number, b: number): number { return a + b; } type AddReturnType = ReturnType<typeof add>; // number
typeof add
是function (a: number, b: number): number
。ReturnType<typeof add>
成功推断出返回类型number
。
Partial
索引类型可以通过映射类型的语法做修改,比如把索引变为可选。
type Partial<T> = { [P in keyof T]?: T[P]; };
1. 参数说明
T
:要处理的对象类型。
2. 映射类型 [P in keyof T]
keyof T
获取T
类型的所有键的联合类型。[P in keyof T]
是一个映射类型,遍历T
的所有键P
。
3. 可选属性 ?
T[P]?
表示将T
的每个属性P
变为可选属性。
类型参数 T 为待处理的类型。
通过映射类型的语法构造一个新的索引类型返回,索引 P 是来源于之前的 T 类型的索引,也就是 P in keyof T,索引值的类型也是之前的,也就是 T[P]。
这样就实现了把索引类型的索引变为可选的效果:
interface User { id: number; profile: { name: string; email: string; }; } type PartialUser = Partial<User>;
User
类型的属性id
和profile
都是必需的。Partial<User>
将这些属性变为可选的,因此PartialUser
的类型是:{ id?: number; profile?: { name: string; email: string; }; }
Required
可以把索引变为可选,也同样可以去掉可选,也就是 Required 类型:
type Required<T> = { [P in keyof T]-?: T[P]; };
1. 参数说明
T
:要处理的对象类型。
2. 映射类型 [P in keyof T]-?
keyof T
获取T
类型的所有键的联合类型。[P in keyof T]-?
是一个映射类型,遍历T
的所有键P
,并将每个属性P
的可选性移除(即变为必需)。
3. 必需属性 -?
T[P]-?
表示将T
的每个属性P
的可选性移除,使其成为必需属性。
类型参数 T 为待处理的类型。
通过映射类型的语法构造一个新的索引类型,索引取自之前的索引,也就是 P in keyof T,但是要去掉可选,也就是 -?,值的类型也是之前的,就是 T[P]。
这样就实现了去掉可选修饰的目的:
interface Person { name?: string; age?: number; address: string; } type RequiredPerson = Required<Person>;
Person
类型的属性name
和age
是可选的,而address
是必需的。Required<Person>
将所有属性变为必需的,因此RequiredPerson
的类型是:{ name: string; age: number; address: string; }
Readonly
同样的方式,也可以添加 readonly 的修饰:
type Readonly<T> = { readonly [P in keyof T]: T[P]; };
类型参数 T 为待处理的类型。
通过映射类型的语法构造一个新的索引类型返回,索引和值的类型都是之前的,也就是 P in keyof T 和 T[P],但是要加上 readonly 的修饰。
Pick
映射类型的语法用于构造新的索引类型,在构造的过程中可以对索引和值做一些修改或过滤。
比如可以用 Pick 实现过滤:
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
1. 参数说明
T
:要从中选择属性的对象类型。K
:要选择的属性键的联合类型,必须是T
的键的子集(即K
必须是keyof T
的一部分)。
2. 映射类型 [P in K]
keyof T
获取T
类型的所有键的联合类型。[P in K]
是一个映射类型,遍历K
中的所有键P
。
3. 属性映射 T[P]
T[P]
表示从T
类型中选择键P
对应的属性类型。
类型参数 T 为待处理的类型,类型参数 K 为要过滤出的索引,通过 extends 约束为只能是 T 的索引的子集。
构造新的索引类型返回,索引取自 K,也就是 P in K,值则是它对应的原来的值,也就是 T[P]。
这样就实现了过滤的目的:
interface Person { name: string; age: number; address: string; } type PersonNameAndAge = Pick<Person, 'name' | 'age'>;
Person
类型的属性name
、age
和address
都是必需的。Pick<Person, 'name' | 'age'>
选择name
和age
属性,因此PersonNameAndAge
的类型是:{ name: string; age: number; }
Record
Record 用于创建索引类型,传入 key 和值的类型:
type Record<K extends keyof any, T> = { [P in K]: T; };
1. 参数说明
K
:键的联合类型,必须是keyof any
的子集,即K
必须是有效的键名。T
:每个键对应的值类型。
2. 映射类型 [P in K]
[P in K]
是一个映射类型,遍历K
中的所有键P
。
3. 属性映射 T
T
表示每个键P
对应的值类型。
type UserStatus = 'active' | 'inactive' | 'pending'; type UserStatusCount = Record<UserStatus, number>; const userStatusCount: UserStatusCount = { active: 10, inactive: 5, pending: 3 };
UserStatusCount
类型的键是'active'
、'inactive'
和'pending'
,每个键的值类型是number
。因此,
userStatusCount
变量必须包含这三个键,并且每个键的值必须是数字。
Exclude
当想从一个联合类型中去掉一部分类型时,可以用 Exclude 类型:
type Exclude<T, U> = T extends U ? never : T;
1. 参数说明
T
:源类型,从中排除某些部分。U
:要排除的类型。
2. 条件类型 T extends U ? never : T
这个条件类型用于检查
T
是否是U
的子类型。如果
T
是U
的子类型,则返回never
,表示从T
中排除这部分。如果
T
不是U
的子类型,则返回T
,表示保留这部分。
联合类型当作为类型参数出现在条件类型左边时,会被分散成单个类型传入,这叫做分布式条件类型。
所以写法上可以简化, T extends U 就是对每个类型的判断。
过滤掉 U 类型,剩下的类型组成联合类型。也就是取差集。
type AllTypes = string | number | boolean | null | undefined; type ExcludeNullish = Exclude<AllTypes, null | undefined>; // ExcludeNullish 的类型是 string | number | boolean
AllTypes
是一个联合类型,包含string
、number
、boolean
、null
和undefined
。Exclude<AllTypes, null | undefined>
排除了null
和undefined
,因此ExcludeNullish
的类型是string | number | boolean
。
Extract
可以过滤掉,自然也可以保留,Exclude 反过来就是 Extract,也就是取交集:
type Extract<T, U> = T extends U ? T : never;
1. 参数说明
T
:源类型,从中提取某些部分。U
:要提取的类型。
2. 条件类型 T extends U ? T : never
这个条件类型用于检查
T
是否是U
的子类型。如果
T
是U
的子类型,则返回T
,表示保留这部分。如果
T
不是U
的子类型,则返回never
,表示排除这部分。
示例
基本示例
type AllTypes = string | number | boolean | null | undefined; type ExtractStrings = Extract<AllTypes, string>; // ExtractStrings 的类型是 string
AllTypes
是一个联合类型,包含string
、number
、boolean
、null
和undefined
。Extract<AllTypes, string>
提取了string
类型,因此ExtractStrings
的类型是string
。
Omit
我们知道了 Pick 可以取出索引类型的一部分索引构造成新的索引类型,那反过来就是去掉这部分索引构造成新的索引类型。
可以结合 Exclude 来轻松实现:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
1. 参数说明
T
:要从中排除属性的对象类型。K
:要排除的属性键的联合类型,必须是keyof any
的子集,即K
必须是有效的键名。
2. 组合使用 Pick
和 Exclude
keyof T
获取T
类型的所有键的联合类型。Exclude<keyof T, K>
从keyof T
中排除K
中的键。Pick<T, Exclude<keyof T, K>>
从T
类型中选择排除后的键。
类型参数 T 为待处理的类型,类型参数 K 为索引允许的类型(string | number | symbol 或者 string)。
通过 Pick 取出一部分索引构造成新的索引类型,这里用 Exclude 把 K 对应的索引去掉,把剩下的索引保留。
这样就实现了删除一部分索引的目的:
interface Person { name: string; age: number; address: string; email: string; } type PersonWithoutEmail = Omit<Person, 'email'>; const person: PersonWithoutEmail = { name: 'John Doe', age: 30, address: '123 Main St' };
Person
类型的属性name
、age
、address
和email
都是必需的。Omit<Person, 'email'>
排除了email
属性,因此PersonWithoutEmail
的类型是:{ name: string; age: number; address: string; }
Awaited
在递归那节我们写过取 Promise 的 ValuType 的高级类型,这个比较常用,ts 也给内置了,就是 Awaited。
它的实现比我们当时写的完善一些:
type Awaited<T> = T extends null | undefined ? T : T extends object & { then(onfulfilled: infer F): any } ? F extends ((value: infer V, ...args: any) => any) ? Awaited<V> : never : T;
1. 参数说明
T
:要从中提取最终值的类型。
2. 条件类型 T extends null | undefined
检查
T
是否是null
或undefined
。如果是,则直接返回
T
。
3. 条件类型 T extends object & { then(onfulfilled: infer F): any }
检查
T
是否是一个对象,并且具有then
方法。then
方法的参数类型被推断为F
。
4. 条件类型 F extends ((value: infer V, ...args: any) => any)
检查
F
是否是一个函数,并且其第一个参数类型被推断为V
。
5. 递归调用 Awaited<V>
如果
F
是一个函数,并且其第一个参数类型为V
,则递归调用Awaited<V>
,继续解析V
。
6. 默认返回 T
如果
T
不是null
或undefined
,且不具有then
方法,则直接返回T
。
类型参数 T 是待处理的类型。
如果 T 是 null 或者 undefined,就返回 T。
如果 T 是对象并且有 then 方法,那就提取 then 的参数,也就是 onfulfilled 函数的类型到 infer 声明的局部变量 F。
继续提取 onfullfilled 函数类型的第一个参数的类型,也就是 Promise 返回的值的类型到 infer 声明的局部变量 V。
递归的处理提取出来的 V,直到不再满足上面的条件。
这样就实现了取出嵌套 Promise 的值的类型的目的:
type Result2 = Awaited<Promise<Promise<number>>>; // number
NonNullable
NonNullable 就是用于判断是否为非空类型,也就是不是 null 或者 undefined 的类型的,实现比较简单:
type NonNullable<T> = T extends null | undefined ? never : T;
1. 参数说明
T
:要从中排除null
和undefined
的类型。
2. 条件类型 T extends null | undefined ? never : T
这个条件类型用于检查
T
是否是null
或undefined
。如果
T
是null
或undefined
,则返回never
,表示从T
中排除这部分。如果
T
不是null
或undefined
,则返回T
,表示保留这部分。
示例
基本示例
type NullableTypes = string | number | null | undefined; type NonNullableTypes = NonNullable<NullableTypes>; // NonNullableTypes 的类型是 string | number
NullableTypes
是一个联合类型,包含string
、number
、null
和undefined
。NonNullable<NullableTypes>
排除了null
和undefined
,因此NonNullableTypes
的类型是string | number
。
Uppercase、Lowercase、Capitalize、Uncapitalize
这四个类型是分别实现大写、小写、首字母大写、去掉首字母大写的。
它们的源码时这样的:
type Uppercase<S extends string> = intrinsic; type Lowercase<S extends string> = intrinsic; type Capitalize<S extends string> = intrinsic; type Uncapitalize<S extends string> = intrinsic;
啥情况,intrinsic 是啥?
这个 intrinsic 是固有的意思,就像 js 里面的有的方法打印会显示 [native code] 一样。这部分类型不是在 ts 里实现的,而是编译过程中由 js 实现的。
其实就是 ts 编译器处理到这几个类型时就直接用 js 给算出来了。
为啥要这样做呢?
因为快啊,解析类型是要处理 AST 的,性能比较差,用 js 直接给算出来那多快呀。
总结
虽然我们学完 6 个套路,各种类型编程逻辑都能写了,但是常用的类型 TS 已经内置了。
这些内置的高级类型用我们学的套路很容易可以实现。
比如用模式匹配可以实现:Parameters、ReturnType
用模式匹配 + 重新构造可以实现:OmitThisParameter
用重新构造可以实现:Partial、Required、Readonly、Pick、Record
用模式匹配 + 递归可以实现: Awaited
用联合类型在分布式条件类型的特性可以实现: Exclude
此外还有 NonNullable 和四个编译器内部实现的类型:Uppercase、Lowercase、Capitalize、Uncapitalize。
这些类型也不咋需要记,就算忘记了自己也能很快的实现。重点还是放在 6 个类型编程的套路上。