「TS类型体操」🔥巧用TS特殊类型特性(下)

130 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情

书接上回,上节我们讲了IsAnyIsEqualIsUnionIsNever以及IsTuple这几个判断ts特殊类型的工具类型实现,这次我们会更进一步,探讨更高级的用法

如果还没看上节的话建议先看看 -- 「TS类型体操」🔥巧用TS特殊类型特性(上)

UnionToIntercetion

这个工具类型的作用是将联合类型转成交叉类型,这是一个逆变的过程,那么ts支持逆变吗?

答案是支持的,在函数参数中,如果一个函数参数有多种类型,那么参数类型会变成这些多个类型的交叉类型,这就是一个逆变的过程

而我们就可以利用这一点,结合联合类型的分布式特性,将它们拆开成单独类型,然后作为函数参数,再用infer从函数参数中提取出结果,得到的就是对应的联合类型了

// 利用 ts 的函数参数类型的逆变特性实现
// 当函数参数有多种类型的时候,会被逆变为它们的交叉类型
// 根据联合类型的分布式特性,将类型拆散后作为函数参数类型
// 再提取函数参数类型即可得到它们的交叉类型
type UnionToIntercetion<U> = (U extends U ? (x: U) => unknown : never) extends (
  x: infer R,
) => unknown
  ? R
  : never

// type Res = {
//     foo: 1;
// } & {
//     bar: 2;
// }
type Res = UnionToIntercetion<{ foo: 1 } | { bar: 2 }>

GetOptional

该工具类型可以获取索引类型(可以理解成是对象)的可选属性,可选属性有什么特点呢?

可选属性最显著的特点就是它的类型是undefined与值存在时的值类型的联合类型

我们就可以利用这一点来进行提取,思路是遍历每个属性,如果{} extends 单个属性成立的话,说明这个属性可能为undefined

// 利用可选属性的类型为 undefined | xxx 的特性
// 判断 {} extends 属性是否成立,成立的话说明属性的类型中有 undefined
// 这时候得到的结果就是 {},所以可以通过这种方式去判断
type GetOptional<Obj extends Record<string, any>> = {
  [P in keyof Obj as {} extends Pick<Obj, P> ? P : never]: Obj[P]
}

interface Foo {
  name: string
  bar: string
  age?: number
  baz?: number
}

// type Res = {
//   age?: number | undefined
//   baz?: number | undefined
// }
type Res = GetOptional<Foo>

GetRequired

趁热打铁,既然可以提取可选属性,那么也可以提取非可选属性,也就是必要属性,刚好和可选属性是对立的,所以我们只需要在对属性的判断逻辑处取反即可

type GetRequired<Obj extends Record<string, any>> = {
  [P in keyof Obj as {} extends Pick<Obj, P> ? never : P]: Obj[P]
}

interface Foo {
  name: string
  bar: string
  age?: number
  baz?: number
}

// type Res = {
//   name: string
//   bar: string
// }
type Res = GetRequired<Foo>

RemoveIndexSignature

现在有下面这样一个索引类型

type Foo = {
  [prop: string]: any
  say(): void
}

这里的[prop: string]就是索引签名,意思是有任意多个字符串索引,而下面的say是具名索引,有自己的名字,说明其为string类型,而索引签名不具有名字,即不可能是string类型

现在我希望实现一个工具类型,能够将索引类型中的索引签名删除掉,也就是我希望得到:

type Foo = {
  say(): void
}

刚刚已经说了,索引类型没有名字,也就是索引不可能是string,可以利用这一点来实现

type Foo = {
  [prop: string]: any
  say(): void
}

type RemoveIndexSignature<T extends Record<string, any>> = {
  [P in keyof T as P extends `${infer Str}` ? Str : never]: T[P]
}

type Res = RemoveIndexSignature<Foo>

ClassPublicProps

ts的类中有publicprivateprotected属性,现在我希望提取出一个类的所有public属性组成一个新的索引类型

首先要知道一点,keyof提取类的属性时,只能提取出public的属性

class Foo {
  public name: string
  private age: number
  protected sex: string

  constructor() {
    this.name = 'Plasticine'
    this.age = 21
    this.sex = 'male'
  }
}

// keyof 只能提取出 public 属性
// 利用这一点来实现提取 public 属性组成新的索引类型
type FooKeys = keyof Foo // "name"

那就很简单了,直接利用keyof遍历所有public属性即可

type ClassPublicProps<T> = {
  [P in keyof T]: T[P]
}

// type Res = {
//   name: string
// }
type Res = ClassPublicProps<Foo>