基本姿勢
keyof
keyof 返回一個類型的所有 key 的聯(lián)合類型:
type KEYS = keyof {
a: string,
b: number
} // a|b
類型索引
類型索引可以通過 key 來獲取對應 value 的類型:
type Value = {a: string, b: number}['a'] // string
特別的酣胀,使用 array[number] 可以獲取數(shù)組/元組中所有值類型的聯(lián)合類型:
type Values = ['a', 'b', 'c'][number] // 'a'|'b'|'c'
in 操作符與類型映射
in 操作符有點類似于值操作中的 for in 操作冯遂,可以遍歷聯(lián)合類型缠劝,結合類型索引可以從一個類型中生成一個新的類型:
// 從 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}
同樣狈究,數(shù)組類型也可以遍歷递递,R in keyof T 的結果為數(shù)組的下標:
type ArrayIndex<T extends any[]> = {
[R in keyof T]: R
}
// 的到一個數(shù)組下標組成的新數(shù)組類型
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 中有一個重要的概念為類型分發(fā)忿族,例如:
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 需要檢測的類型為泛型聯(lián)合類型時错英,會將聯(lián)合類型中的每一個類型分別進行檢測。因此 '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
椭岩。可以將聯(lián)合類型與 extends 結合使用達到循環(huán)的效果璃赡。如果要阻止類型分發(fā)判哥,只需要在外面套一個數(shù)組即可:
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
如果很多時候我們既需要類型分發(fā)后的類型,還需要類型分發(fā)前的聯(lián)合類型碉考。例如如果我們判斷一個類型是否為聯(lián)合類型塌计,那么可以:
type IsUnion<T> = T extends T ? [Exclude<T, T>] extends [never] ? false: true : never
即如果一個類型是聯(lián)合類型,那么 execlude 掉一個其中的類型后其類型不會為 never侯谁。否則就為 never夺荒。但是在 Exclude 中出現(xiàn)了兩 T 這明顯是不行的瞒渠。因此可以利用 TS 的默認類型:
type IsUnion<T, R=T> = T extends any ? [Exclude<R, T>] extends [never] ? false: true : never
這種方法可以用在既需要分發(fā)后的類型也需要原始類型的情況。
此外技扼,extends 還有另一個需要注意的地方伍玖,泛型變量無法直接與 never 比較,需要套一個數(shù)組剿吻,例如:
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
數(shù)組/元組類型
數(shù)組類型可以使用 ... 操作符進行展開:
type Add<S extends any[], R> = [...S, R]
type Y3 = Add<[1, 2, 3], 4> // [1, 2, 3, 4]
元組表示不可修改的數(shù)組,可以使用 as const 將數(shù)組轉換為元組茅撞。
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 類型操作符中不存在循環(huán)表達式帆卓,但是可以使用遞歸來進行循環(huán)操作,例如:
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]