TypeScript笔记(四)-- 索引类型、映射类型及辅助泛型

1,388 阅读4分钟

索引类型(keyof)

  function pluck(obj, keys) {
    return keys.map(key => obj[key])
  }

上面这个函数的目的是通过遍历 keys 来获取 obj 里面的值,最后返回包含所有值的数组,我们希望 keys 的所有 key 都是 obj 拥有的,如果要使用 ts 来做类型约束的话,用常规方法很难实现,这时就可以使用索引类型来实现:

  function pluck<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
    return keys.map(key => obj[key])
  }
  
  interface Person {
    name: string
    age: number
  }
  let person: Person = {
    name: 'Jarid',
    age: 35
  }
  let strings = pluck(person, ['name', 'age'])

我们通过泛型及keyof来实现类型约束的,keyof就是索引类型查询操作符keyof T就是获取泛型T的所有属性名的集合,在上面例子中就是'name' | 'age',这样我们就获取到了 Person 上所有 key 组成的联合类型。T[K]索引访问,获取T所有存在于K的属性的类型组成的联合类型。

映射类型(in)

  type Keys = 'name' | 'hobby'
  type Person = { [K in Keys]: string }

ts 中 in 的用法和 for...in 类似,会对 Keys 进行遍历赋值,然后返回结果,上面这个例子得到的结果就是:js

  type Person = {
    name: string
    hobby: string
  }

我们可以结合索引类型和映射类型来实现一些工具类泛型,例如实现一个将传入类型的所有属性设置成可选属性,然后返回处理后的类型:

  type Optional<T> = {
    [P in keyof T]?: T[P]
  }
  type Search = {
      name: string
      age: number
  }
  type OptionalSearch = Optional<Person>

[P in keyof T]就是遍历T中的 key,T[P]就是当前 key 的类型,然后我们在遍历的 key 后面加上?就是将这个属性变成可选属性,这样我们在调用这个泛型的时候,就会将传入的类型的所有属性变成可选属性并返回处理好后的新类型。

辅助泛型

结合索引类型和映射类型能实现很多很方便实用的泛型,其实在 TS 已经内置了一些常用的辅助泛型。

Partial(可选)

  /**
   * Make all properties in T optional
   */
  type Partial<T> = {
    [P in keyof T]?: T[P]
  }

Partial<T>T的所有属性变成可选的:

  • [P in keyof T]通过映射类型,遍历T上所有的属性
  • ?:将属性设置为可选的
  • T[P]通过索引访问将类型设置为原来的类型

Required(必选)

  /**
   * Make all properties in T required
   */
  type Required<T> = {
    [P in keyof T]-?: T[P]
  }

这个泛型的作用和Partial刚好相反,是将传入类型的所有属性变成必选的,-?的意思就是将属性的?去掉,这样该属性自然就变成必选属性了。

Readonly(只读)

  /**
   * Make all properties in T readonly
   */
  type Readonly<T> = {
    readonly [P in keyof T]: T[P];
  }

Readonly<T>T的所有属性变成只读的

Exclude(排除)

  /**
   * Exclude from T those types that are assignable to U
   */
  type Exclude<T, U> = T extends U ? never : T

Exclude<T, U>T中排除存在于U的类型后组成的联合类型

  • 通过遍历T中的所有子类型,如果该类型存在于U,则返回never,否则返回该子类型,最终得到的便是T排除了U中所有类型后的联合类型。
  • never表示一个不存在的类型,与其他类型联合后,never会被去除掉
  • type E = Exclude<'a' | 'b' | 'd', 'b' | 'c'>,结果为:type E = 'a' | 'd'

Extract(提取)

  /**
   * Extract from T those types that are assignable to U
   */
  type Extract<T, U> = T extends U ? T : never

Extract<T, U>提取同时存在于联合类型TU的类型组成的联合类型
这个泛型的作用和Exclude刚好相反,例如type E = Extract<'a' | 'b' | 'd', 'b' | 'c'>,这次我们得到的结果是type E = 'b'

Pick(筛选)

  /**
   * 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]
  }

Pick<T, K extends keyof T>T筛选存在于K的类型

  • K extends keyof T K的类型必须存在于T
  • [P in K]遍历K的所有类型
  • T[P]通过索引访问将类型设置为原来的类型

Omit(过滤)

  /**
   * 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>>

Omit<keyof T, K>就是从T中将K中所有属性过滤

  • Omit是结合PiceExclude实现的,效果和Pick相反
  • 先通过Exclude<keyof T, K>T的索引类型集合中排除K的所有类型
  • 然后通过PickT中筛选出排除后的所有类型 Example:
  interface Coder {
    id: string,
    name: string,
    age: number,
    hobby: string[]
  }

  const JSer: Omit<Coder, 'name' | 'age'> = {
    id: '1001',
    hobby: ["code", "羽毛球"]
  }

Record(记录)

  /**
   * Construct a type with a set of properties K of type T
   */
  type Record<K extends keyof any, T> = {
    [P in K]: T;
  }

Record<K extends keyof any, T>遍历K的每一项属性,类型设置为T
Example:

  interface Obj {
    title: string,
    value: number
  }
  
  const List: Record<string, Obj> = {
    home: { title: '首页', value: 1 },
    search: { title: '搜索', value: 2 }
  }

ReturnType(返回值类型)

  /**
   * Obtain the return type of a function type
   */
  type ReturnType<T extends (...args: any) => any> = T extends (
    ...args: any
  ) => infer R
    ? R
    : any

ReturnType<T extends (...args: any) => any> 返回传入函数类型T的返回值类型

  • T extends (...args: any) => any 约束参数T必须是函数类型
  • infer关键词的作用是让TS自己推导类型,并将推导结果存储在infer后面的参数类型上
  • infer关键词只能在extends条件类型上使用
  • 这里的实现方式就是先判断T是否是函数类型并推导返回值类型存储在R上,是则返回R,不是则返回never