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 个类型编程的套路上。