如何理解 TS 類型編程中的 extends 和 infer
- 2021 年 12 月 29 日
- 筆記
- typescript
extends
extends 在TS類型編程中用法(T extends U),表示 T 中的某些在 U 裡面,比較難描述,用法如下:
T extends U ? X : Y
分為兩種情況理解更直觀一些:
1)如果 T 不是一個聯合類型,表示如果 T 是 U 的子集,那麼返回 X 否則返回 Y。
舉個例子,在下面的例子中,如果 T 是 U 的子集,那麼返回 number,否則返回 never。
export type TExtends<T, U> = T extends U ? number : never;
// T(number)是 U(number | string)的子集,所以返回number
type TExtendsExample1 = TExtends<number, number | string>; // number
// T(boolean) 不是 U(number | string)的子集,所以返回never
type TExtendsExample2 = TExtends<boolean, number | string>; // never
2)如果 T 是一個聯合類型,表示如果 T 中的類型是 U 的子集,那麼返回 X 否則返回 Y。這個過程可以理解為對T中的類型進行一次遍歷,每個類型都執行一次 extends。
舉個例子:
下面的例子中對 T 中的每個類型進行遍歷,檢測是否是 extends 於 null 或 undefined。如果滿足這個條件就返回 never(對於聯合類型來說如果返回never那就相當於不存在,因為never是所有類型的子類型),如果不滿足就返回原來的類型。
type NonNullable<T> = T extends null | undefined ? never : T;
// T(number | string) 不是 U(null | undefined) 的子集,所以返回 T
type TNonNullableExample1 = NonNullable<number | string>; // number | string
// T(string | null) 中 string 不是 U 的子集返回 string,null 是 U 的子集,返回 never
type TNonNullableExample2 = NonNullable<string | null>; // string
infer
infer 可以推斷一個類型變數,常見用法示例:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
infer R 相當於聲明一個類型變數,這個變數的類型取決於傳入的泛型 T,在之前的時候 extends 右邊的類型是寫死的,但是在這裡通過 infer R 來代替寫死的類型,並且具體的類型取決於傳入的泛型。
不過需要注意,R 變數只能在 true 的分支可以使用,也就是只能在 ? 的第一個分支中使用。
下面通過兩個例子進行說明:
1)如果 T 是 () => infer R 的子集,那麼返回 R,否則返回 number
{
type Func<T> = T extends () => infer R ? R : number;
// T(string) 不是 () => infer R 的子集,因此返回 number
type TFuncExample1 = Func<string>; // number
// T(() => boolean) 是 () => infer R 的子集,並且通過傳入的() => boolean可以推斷出 R 是 boolean,因此返回 R(boolean)
type TFuncExample2 = Func<() => boolean>; // boolean
}
2)如果 T 是 {a: infer VType, b: infer VType}
的子集,那麼返回 UType | VType
,否則返回number。
type TObj<T> = T extends { a: infer VType, b: infer UType} ? VType | UType : number;
// T(string)不是 {a: infer VType, b: infer UType} 的子集,因此返回 number
type TObjExample1 = TObj<string>; // number
// T(string)是 {a: infer VType, b: infer UType} 的子集,因此返回 UType | VType
type TObjExample2 = TObj<{ a: number, b: string }>; // => number | string
完。