在 TypeScript 中条件类型的用法是:
T extends U ? X : Y
跟 JS 中的条件表达式一样,如果extends
语句为真,则取X
类型 ,反之得到Y
类型 。我们这里把X
称为条件类型的真分支,Y
称为假分支。
现在,在 TypeScript 2.8 之后,我们可以在 extends
条件语句中使用infer
关键字引入一个变量表示推断的类型,这个变量可以被用在真分支中,也就是说infer
实际上是一个声明关键字,我们可以用它来声明一个变量,而该变量表示的是 infer
所处位置的类型。以标准库的 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>
接收一个任意函数,在extends
分支把推断的函数返回值的类型赋给变量R
,从而得到该类型。这里需要注意的是,我们只能在extends
条件语句中使用infer
关键字,不能在诸如类型参数这样的地方使用它:
type ReturnType<T extends (...args: any[]) => infer R> = R; // Error.
既然可以获取到函数的返回值的类型,同样也可以推断出函数的参数类型,标准库的Paramaters<T>
把函数的每个参数类型提取到一个元组中:
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]
除了把函数参数列表的类型提取出来,进一步深入,假如函数的参数是单个对象,我们可以利用infer
把参数的结构推断出来:
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,
}
这里我们先定义出参数类型为单个对象的通用函数FunctionWithMappedArgument<T>
,接着定义解构参数的方法DestructuredArguments<T>
,这个方法做的事情是接收一个FunctionWithMappedArgument<T>
类型的函数,然后把函数的泛型参数T
推断为新的变量R
,这样编译器就会替我们计算出R
的解构。
通过上面的代码,我们就可以把函数的参数类型提取出来,无需再声明一个类型,编码过程中编辑器的智能感知也能很好地进行代码补全提示。
多处 infer 推断一个变量
上面展示的例子都只用到了一个infer
,在extends
条件语句中,我们可以有多个infer
,只不过它们只能作用于同一个变量,根据推断位置的不同产生的类型也有所不同。
在共变的位置,会推断出联合类型:
type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
type T10 = Foo<{ a: string, b: string }>; // string
type 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 }>; // string
type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number
函数重载中的推断
当作用于一个有多处调用签名(函数重载)的类型时,infer
只对最后一个签名生效,不可能基于参数列表的不同进行重载。
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