刷完了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
  1. Person['key']查询类型(Lookup Types), 可以获取到对应属性类型的类型;
  2. Person[keyof Person]本质上是执行 Person['id' | 'name' | 'age']
  3. 由于联合类型具有分布式的特性,Person['id' | 'name' | 'age'] 变成了 Person['id'] | Person['name'] | Person['age']
  4. 最后得到的结果就是 number | string.
约束范型参数的范围
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };
type P3 = MyPick<Person, 'id' | 'age'>
  1. K extends keyof TK进行了约束,只能是'id','name','age'中的一个类型或者几个类型组成的联合类型;
  2. 如果没有这个约束,{ [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; }
  1. [P in keyof T]是对所有属性的键值类型进行遍历,案例中得到的P 分别是'id','name''age';
  2. T[P]是查询类型,上面介绍过了,Person['id'] 的结果是numberPerson['name'] 的结果是stringPerson['age'] 的结果是number
  3. 将每个属性类型添加readonly修饰符,最后的结果就是 { readonly id: number; readonly name: string; readonly age: number; }
  • 去掉对象类型的某些属性

微软官是通过Pickexclude组合来实现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 的部分叫做 条件类型(后面也会单独介绍)。代码中的含义就是如果 PF的属性类型,则取F[P],如果PS的属性类型,则取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;
};

这个实现逻辑涉及到了其他的知识点有点复杂,没完全看懂没关系,后面会介绍。