在條件類型中使用 infer 關(guān)鍵字

在 TypeScript 中條件類型的用法是:
T extends U ? X : Y跟 JS 中的條件表達(dá)式一樣,如果 extends 語句為真,則取X類型 ,反之得到Y(jié)類型 。我們這里把X稱為條件類型的真分支,Y 稱為假分支。
現(xiàn)在,在 TypeScript 2.8 之后,我們可以在 extends 條件語句中使用 infer 關(guān)鍵字引入一個變量表示推斷的類型,這個變量可以被用在真分支中,也就是說 infer 實(shí)際上是一個聲明關(guān)鍵字,我們可以用它來聲明一個變量,而該變量表示的是 infer 所處位置的類型。
以標(biāo)準(zhǔn)庫的 ReturnType 為例:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;const getFullName = (firstName: string, lastName: string): string {return `${firstName}_${lastName}`;}const fullName: ReturnType<typeof getFullName> = getFullName('foo', 'bar');
這里 ReturnType<T> 接收一個任意函數(shù),在 extends 分支把推斷的函數(shù)返回值的類型賦給變量 R,從而得到該類型。這里需要注意的是,我們只能在 extends 條件語句中使用 infer 關(guān)鍵字,不能在諸如類型參數(shù)這樣的地方使用它:
type ReturnType<T extends (...args: any[]) => infer R> = R; // Error.既然可以獲取到函數(shù)的返回值的類型,同樣也可以推斷出函數(shù)的參數(shù)類型,標(biāo)準(zhǔn)庫的Paramaters<T>把函數(shù)的每個參數(shù)類型提取到一個元組中:
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;const getFullName = (firstName: string, lastName: string, age: number): string =>{return `${firstName}_${lastName}`;}type params = Parameters<typeof getFullName> // [string, string, number]
除了把函數(shù)參數(shù)列表的類型提取出來,進(jìn)一步深入,假如函數(shù)的參數(shù)是單個對象,我們可以利用 infer 把參數(shù)的結(jié)構(gòu)推斷出來:
type FunctionWithMappedArgument<P extends { [key: string]: any }> = (args: P) => any;type DestructuredArguments<F extends FunctionWithMappedArgument<any>> = F extends FunctionWithMappedArgument<infer R> ? R : never;declare function drawPoint(config: { x: number, y: number, color: string}): any;const args: DestructuredArguments<typeof drawPoint> = {x: 4,y: 6,}
這里我們先定義出參數(shù)類型為單個對象的通用函數(shù) FunctionWithMappedArgument<T>,接著定義解構(gòu)參數(shù)的方法 DestructuredArguments<T>,這個方法做的事情是接收一個FunctionWithMappedArgument<T> 類型的函數(shù),然后把函數(shù)的泛型參數(shù)T推斷為新的變量 R,這樣編譯器就會替我們計算出 R 的解構(gòu)。
通過上面的代碼,我們就可以把函數(shù)的參數(shù)類型提取出來,無需再聲明一個類型,編碼過程中編輯器的智能感知也能很好地進(jìn)行代碼補(bǔ)全提示。
多處 infer 推斷一個變量
上面展示的例子都只用到了一個 infer,在 extends 條件語句中,我們可以有多個 infer,只不過它們只能作用于同一個變量,根據(jù)推斷位置的不同產(chǎn)生的類型也有所不同。
在共變的位置,會推斷出聯(lián)合類型:
type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;type T10 = Foo<{ a: string, b: string }>; // stringtype T11 = Foo<{ a: string, b: number }>; // string | number
在逆變的位置,推斷的是交叉類型:
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 }>; // stringtype T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number
函數(shù)重載中的推斷
當(dāng)作用于一個有多處調(diào)用簽名(函數(shù)重載)的類型時,infer 只對最后一個簽名生效,不可能基于參數(shù)列表的不同進(jìn)行重載。
declare function foo(x: string): number;declare function foo(x: number): string;declare function foo(x: string | number): string | number;type T30 = ReturnType<typeof foo>; // string | number
