TS 类型体操合集

基本姿势

keyof

keyof 返回一个类型的所有 key 的联合类型:

type KEYS = keyof {
    a: string,
    b: number
} // a|b

类型索引

类型索引可以通过 key 来获取对应 value 的类型:

type Value = {a: string, b: number}['a'] // string

特别的,使用 array[number] 可以获取数组/元组中所有值类型的联合类型:

type Values = ['a', 'b', 'c'][number] // 'a'|'b'|'c'

in 操作符与类型映射

in 操作符有点类似于值操作中的 for in 操作,可以遍历联合类型,结合类型索引可以从一个类型中生成一个新的类型:

// 从 T 中 pick 出一个或多个 key 组成新的类型
type MyPick<T, S extends keyof T> = {
    [R in S] : T[R]
} 
type PartType = MyPick<{a: string, b: number, c: number}, 'a'|'b'> // {a: string, b: number}

同样,数组类型也可以遍历,R in keyof T 的结果为数组的下标:

type ArrayIndex<T extends any[]> = {
    [R in keyof T]: R
}
// 的到一个数组下标组成的新数组类型
type Indexes = ArrayIndex<['a', 'b', 'c']> // ['0', '1', '2']

extends

extends 类型于值运算符中的三元表达式:

S extends T ? K : V

若 S 兼容 T 则返回类型 K 否则返回类型 V,例如:

type Whether = "a" extends "a"|"b" ? true : false // true
type Whether2 = {a: string} extends {b: string} ? true : false // false

extends 中有一个重要的概念为类型分发,例如:

type Filter<T, S> = T extends S ? never : T
type X = Filter<'a'|'b'|'c', 'c'> // 'a'|'b'

从直观上来看 Filter 的作用是计算 'a'|'b'|'c' extends 'c' 这个表达式显然不成立,应该返回 never。但是实际上返回了 'a'|'b'。这是由于当 extends 需要检测的类型为泛型联合类型时,会将联合类型中的每一个类型分别进行检测。因此 'a'|'b'|'c' extends 'c' 实际等价于:

'a' extends 'c' ? never : T | 'b' extends 'c' ? never : T | 'c' extends 'c' ? never : T 
  = 'a' | 'b' | never 
  = 'a' | 'b' 

这里也包含了另外一个知识点,xxx|never=xxx。可以将联合类型与 extends 结合使用达到循环的效果。如果要阻止类型分发,只需要在外面套一个数组即可:

type Filter<T, S> = T extends S ? never : T
type X = Filter<'a'|'b'|'c', 'c'> // 'a'|'b'

type Filter<T, S> = [T] extends [S] ? never : T
type X = Filter<'a'|'b'|'c', 'c'> // never

如果很多时候我们既需要类型分发后的类型,还需要类型分发前的联合类型。例如如果我们判断一个类型是否为联合类型,那么可以:

type IsUnion<T> = T extends T ? [Exclude<T, T>] extends [never] ? false: true : never

即如果一个类型是联合类型,那么 execlude 掉一个其中的类型后其类型不会为 never。否则就为 never。但是在 Exclude 中出现了两 T 这明显是不行的。因此可以利用 TS 的默认类型:

type IsUnion<T, R=T> = T extends any ? [Exclude<R, T>] extends [never] ? false: true : never

这种方法可以用在既需要分发后的类型也需要原始类型的情况。

此外,extends 还有另一个需要注意的地方,泛型变量无法直接与 never 比较,需要套一个数组,例如:

type IsNever<T> = T extends never ? true: false
type Y = IsNever<never> // never

type IsNever<T> = [T] extends [never] ? true: false
type Y = IsNever<never> // true

infer

infer 可以类比到值元算的类型匹配,在类型体操中有非常多的应用。例如对于 scala:

a match {
case Success(val) => val
case _ => None
}

当 a 值为 Success() 类型时提取其中的 val。利用 infer 也可以达到相同的效果:

type ExtractType<T> = T extends {a: infer R} ? R : never // 匹配成功返回 R 否则返回 never
ExtractType<{a: {b: string}}> // {b: string}

可以看出先定义了一个模板 {a: infer R} 然后用于匹配类型 {a: {b: string}},这时就可以得到 R = {b: string}。目前 infer 出来的类型仅能应用到 extends 的成功分支
infer 也可以用匹配字面量的类型,例如:

type Startswith<T, S extends string> = T extends `${S}${infer R}` ? true : false
Startswith<"hello world", "hello"> // true
Startswith<"hello world", "world"> // false

type Strip<T, S extends string> = T extends `${S}${infer R}` ? R : T
type Y1 = Strip<"hello world", "hello "> // world
type Y2 = Strip<"hello world", "world"> // hello world

数组/元组类型

数组类型可以使用 ... 操作符进行展开:

type Add<S extends any[], R> = [...S, R]
type Y3 = Add<[1, 2, 3], 4> // [1, 2, 3, 4]

元组表示不可修改的数组,可以使用 as const 将数组转换为元组。

const array1 = [1, 2, 3, 4]
type X1 = typeof array1 // number[]
type X2 = X1[number] // number

const array2 = [1, 2, 3, 4] as const
type Y1 = typeof array2 // readonly [1, 2, 3, 4]
type Y2 = Y1[number] // 1|2|3|4

递归类型

在 typescript 类型操作符中不存在循环表达式,但是可以使用递归来进行循环操作,例如:

type TrimLeft<T extends string> = T extends ` ${infer R}`? TrimLeft<R>: T
type Y7 = TrimLeft<'  Hello World  '> // Hello World  

type Concat<S extends any[]> = S extends [infer R, ...infer Y] ? `${R & string}${Concat<Y>}` : ''
type Y6 = Concat<['1', '2', '3']>

type Join<S extends any[], T extends string> = S extends [infer R, ...infer Y] ? 
                                                (Y['length'] extends 0 ? R: `${R & string}${T}${Join<Y, T>}`)  : ''
                                               
type Y4 = Join<['1', '2', '3'], '-'> // '1-2-3'


type Flatten<S extends any[]> = S extends [infer R, ...infer Y] ? 
    (R extends any[] ? [...Flatten<R>, ...Flatten<Y>] : [R, ...Flatten<Y>]) : []
type Y3 = Flatten<[[1], 2, [3, 4, [5], [6, [7, 8]]]]> // [1, 2, 3, 4, 5, 6, 7 ,8]
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容