infer
最先出如今此 PR 中,表示在 extends
條件語句中待推斷的類型變量。html
簡單示例以下:git
type ParamType<T> = T extends (param: infer P) => any ? P : T;
複製代碼
在這個條件語句 T extends (param: infer P) => any ? P : T
中,infer P
表示待推斷的函數參數。github
整句表示爲:若是 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
有關的映射類型:typescript
用於提取函數類型的返回值類型:數組
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
複製代碼
用於提取構造函數中參數(實例)類型:app
一個構造函數可使用 new
來實例化,所以它的類型一般表示以下:async
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。
前段時間,在 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>
。具體答案就不給出了,感興趣的小夥伴能夠嘗試一下。
更多文章,請關注咱們的公衆號: