刷完了type-challenges的所有简单和中等难度的题目后,对TypeScript的类型操作有了一些新的理解和认识。特此用几篇文章来记录下一些重要的知识点。
本系列文章需要您对TypeScript有基本的了解
基本用法
JavaScript通过Object.keys()
获取对象的所有属性键值,而typescript主要关注的是类型操作,通过 keyof
操作符可以获取对象中的所有键类型组成的联合类型。
为了具体了解keyof
操作符的作用,我们通过一些例子来解释下:
type Person = {
id: number;
name: string;
age: number;
};
type P1 = keyof Person; //'id' | 'name' | 'age'
keyof
操作符得到的是Person
类型的所有键值类型即'id'
,'name'
和'age'
三个字面量类型组成的联合类型'id' | 'name' | 'age'
。
实际应用
接下来我会用一些例子讲解keyof
的应用。
获取对象所有属性的类型
type P2 = Person[keyof Person]; // number | string
Person['key']
是查询类型(Lookup Types), 可以获取到对应属性类型的类型;Person[keyof Person]
本质上是执行Person['id' | 'name' | 'age']
;- 由于联合类型具有分布式的特性,
Person['id' | 'name' | 'age']
变成了Person['id'] | Person['name'] | Person['age']
;- 最后得到的结果就是
number | string
.
约束范型参数的范围
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };
type P3 = MyPick<Person, 'id' | 'age'>
K extends keyof T
对K
进行了约束,只能是'id','name','age'
中的一个类型或者几个类型组成的联合类型;- 如果没有这个约束,
{ [P in K]: T[P] }
则会报错。
和映射类型组合实现某些功能
- 给对象类型的所有属性加上
readonly
修饰符
type MyReadonly<T> = { readonly [P in keyof T]: T[P] };
type P4 = MyReadonly<Person>; // { readonly id: number; readonly name: string; readonly age: number; }
[P in keyof T]
是对所有属性的键值类型进行遍历,案例中得到的P
分别是'id'
,'name'
和'age'
;T[P]
是查询类型,上面介绍过了,Person['id']
的结果是number
,Person['name']
的结果是string
,Person['age']
的结果是number
。- 将每个属性类型添加
readonly
修饰符,最后的结果就是{ readonly id: number; readonly name: string; readonly age: number; }
- 去掉对象类型的某些属性
微软官是通过Pick
和exclude
组合来实现Omit
逻辑的,我们可以通过以下的代码实现同样的功能。
type MyOmit<T, K> = { [P in keyof T as P extends K ? never : P]: T[P] };
type P5 = MyOmit<Person, 'id' | 'name'> // {age: number;}
代码中的
as P extends K ? never : P
这部分代码叫做重映射 ,因为我们不一定需要的是P
,有些情况下需要对P
进行一些转换;案例中K
中包含的P
键值类型则通过never
忽略了,相反则保留。所以最后的结果是{age: number;}
- 给对象类型添加新的属性
type AppendToObject<T, U extends keyof any, V> = {[P in keyof T | U]: P extends keyof T ? T[P] : V}
type P6 = AppendToObject<Person, 'address', string> // { address: string; id: number; name: string; age: number; }
和条件类型组合实现功能
- 两个对象类型合并成一个新的类型
type Merge<F extends Record<string, any>, S extends Record<string, any>> = {
[P in keyof F | keyof S]: P extends keyof S ? S[P] : P extends keyof F ? F[P] : never;
};
type Skill = {
run: () => void;
}
type P7 = Merge<Person, Skill>; // { id: number; name: string; age: number; run: () => void; }
案例中
P extends keyof S ? X : Y
的部分叫做条件类型
(后面也会单独介绍)。代码中的含义就是如果P
是F
的属性类型,则取F[P]
,如果P
是S
的属性类型,则取S[P]
。
小结
经过前面的介绍,应该对keyof
的使用有一些感觉了。下面我列一些代码,大家可以感受下:
type _DeepPartial<T> = { [K in keyof T]?: _DeepPartial<T[K]> }
type Diff<T extends Record<string, any>, U extends Record<string, any>> = {
[P in keyof U | keyof T as P extends keyof U
? P extends keyof T
? never
: P
: P extends keyof T
? P
: never]: P extends keyof U ? U[P] : P extends keyof T ? T[P] : never;
};
这个实现逻辑涉及到了其他的知识点有点复杂,没完全看懂没关系,后面会介绍。