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 复制代码
用于提取构造函数中参数(实例)类型:markdown
一个构造函数可使用 new
来实例化,所以它的类型一般表示以下:app
type Constructor = new (...args: any[]) => any; 复制代码
当 infer
用于构造函数类型中,可用于参数位置 new (...args: infer P) => any;
和返回值位置 new (...args: any[]) => infer P;
。async
所以就内置以下两个映射类型:
// 获取参数类型 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>
。具体答案就不给出了,感兴趣的小伙伴能够尝试一下。
更多文章,请关注咱们的公众号: