基本姿势
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]