介紹
infer
最早出現在此 PR 中,表示在 extends
條件語句中待推斷的型別變數。
簡單示例如下:
type ParamType<T> = T extends (param: infer P) => any ? P : T;
複製程式碼
在這個條件語句 T extends (param: infer P) => any ? P : T
中,infer P
表示待推斷的函式引數。
整句表示為:如果 T
能賦值給 (param: infer P) => any
,則結果是 (param: infer P) => any
型別中的引數 P
,否則返回為 T
。
interface User {
name: string;
age: number;
}
type Func = (user: User) => void
type Param = ParamType<Func>; // Param = User
type AA = ParamType<string>; // string
複製程式碼
內建型別
在 2.8 版本中,TypeScript 內建了一些與 infer
有關的對映型別:
-
用於提取函式型別的返回值型別:
type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any; 複製程式碼
相比於文章開始給出的示例,
ReturnType<T>
只是將infer P
從引數位置移動到返回值位置,因此此時P
即是表示待推斷的返回值型別。type Func = () => User; type Test = ReturnType<Func>; // Test = User 複製程式碼
-
用於提取建構函式中引數(例項)型別:
一個建構函式可以使用
new
來例項化,因此它的型別通常表示如下:type Constructor = new (...args: any[]) => any; 複製程式碼
當
infer
用於建構函式型別中,可用於引數位置new (...args: infer P) => any;
和返回值位置new (...args: any[]) => infer P;
。因此就內建如下兩個對映型別:
// 獲取引數型別 type ConstructorParameters<T extends new (...args: any[]) => any> = T extends new (...args: infer P) => any ? P : never; // 獲取例項型別 type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any; class TestClass { constructor( public name: string, public string: number ) {} } type Params = ConstructorParameters<typeof TestClass>; // [string, numbder] type Instance = InstanceType<typeof TestClass>; // TestClass 複製程式碼
一些用例
至此,相信你已經對 infer
已有基本瞭解,我們來看看一些使用它的「騷操作」:
-
tuple 轉 union ,如:
[string, number]
->string | number
解答之前,我們需要了解 tuple 型別在一定條件下,是可以賦值給陣列型別:
type TTuple = [string, number]; type TArray = Array<string | number>; type Res = TTuple extends TArray ? true : false; // true type ResO = TArray extends TTuple ? true : false; // false 複製程式碼
因此,在配合
infer
時,這很容做到:type ElementOf<T> = T extends Array<infer E> ? E : never type TTuple = [string, number]; type ToUnion = ElementOf<TTuple>; // string | number 複製程式碼
在 stackoverflow 上看到另一種解法,比較簡(牛)單(逼):
type TTuple = [string, number]; type Res = TTuple[number]; // string | number 複製程式碼
-
union 轉 intersection,如:
string | number
->string & number
這個可能要稍微麻煩一點,需要
infer
配合「 Distributive conditional types 」使用。在相關連結中,我們可以瞭解到「Distributive conditional types」是由「naked type parameter」構成的條件型別。而「naked type parameter」表示沒有被
Wrapped
的型別(如:Array<T>
、[T]
、Promise<T>
等都是不是「naked type parameter」)。「Distributive conditional types」主要用於拆分extends
左邊部分的聯合型別,舉個例子:在條件型別T extends U ? X : Y
中,當T
是A | B
時,會拆分成A extends U ? X : Y | B extends U ? X : Y
;有了這個前提,再利用在逆變位置上,同一型別變數的多個候選型別將會被推斷為交叉型別的特性,即
type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never; type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>; // string type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number 複製程式碼
因此,綜合以上幾點,我們可以得到在 stackoverflow 上的一個答案:
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; type Result = UnionToIntersection<string | number>; // string & number 複製程式碼
當傳入
string | number
時:-
第一步:
(U extends any ? (k: U) => void : never)
會把 union 拆分成(string extends any ? (k: string) => void : never) | (number extends any ? (k: number)=> void : never)
,即是得到(k: string) => void | (k: number) => void
; -
第二步:
(k: string) => void | (k: number) => void extends ((k: infer I)) => void ? I : never
,根據上文,可以推斷出I
為string & number
。
-
當然,你可以玩出更多花樣,比如 union 轉 tuple。
LeetCode 的一道 TypeScript 面試題
前段時間,在 GitHub 上,發現一道來自 LeetCode TypeScript 的面試題,比較有意思,題目的大致意思是:
假設有一個這樣的型別(原題中給出的是類,這裡簡化為 interface):
interface Module {
count: number;
message: string;
asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>;
syncMethod<T, U>(action: Action<T>): Action<U>;
}
複製程式碼
在經過 Connect
函式之後,返回值型別為
type Result {
asyncMethod<T, U>(input: T): Action<U>;
syncMethod<T, U>(action: T): Action<U>;
}
複製程式碼
其中 Action<T>
的定義為:
interface Action<T> {
payload?: T
type: string
}
複製程式碼
這裡主要考察兩點
- 挑選出函式
- 條件型別 + 此篇文章所提及的
infer
挑選函式的方法,已經在 handbook 中已經給出,只需判斷 value 能賦值給 Function 就行了:
type FuncName<T> = {
[P in keyof T]: T[P] extends Function ? P : never;
}[keyof T];
type Connect = (module: Module) => { [T in FuncName<Module>]: Module[T] }
/*
* type Connect = (module: Module) => {
* asyncMethod: <T, U>(input: Promise<T>) => Promise<Action<U>>;
* syncMethod: <T, U>(action: Action<T>) => Action<U>;
* }
*/
複製程式碼
接下來就比較簡單了,主要是利用條件型別 + infer
,如果函式可以賦值給 asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>
,則取值為 asyncMethod<T, U>(input: T): Action<U>
。具體答案就不給出了,感興趣的小夥伴可以嘗試一下。
更多
參考
更多文章,請關注我們的公眾號: