TypeScript 泛型:参数类型推断

Jer*_*yow 7 typescript

考虑以下代码:

function ensure<TModel, TValue>(accessor: { (obj: TModel): TValue; }) { 
}

interface Person {
    firstName: string;
    lastName: string;
}

ensure((p: Person) => p.firstName);  // <-- this works
ensure<Person>(p => p.firstName);    // <-- this does not work
Run Code Online (Sandbox Code Playgroud)

为什么最后一行是语法错误?

提供的参数与调用目标的任何签名都不匹配。

为什么 p 被推断为类型any而不是Person

这是TypeScript 游乐场中代码的链接

nii*_*ani 9

(另一个编辑:部分类型参数推断被废弃/延迟,并且从未进入 TS3.1 或任何版本,直到并包括 TS3.7。哦,好吧)

编辑:TypeScript 3.1 即将推出的功能将允许部分类型参数推断(使您引用的示例工作),有关更多详细信息,请参阅拉取请求

原始答案(适用于 TypeScript < 3.1):

第一个示例起作用的原因是编译器从传入的匿名 lambda 函数的类型推断出这两个泛型。

不幸的是,在 TypeScript 中使用泛型函数时,要么全有要么全无——你必须提供:

  • 匹配函数签名的所有泛型的类型,或
  • 没有泛型,如果您希望编译器“猜测”与您的调用最匹配的函数签名,同时自动推断类型(如果这种推断是可能的)

请注意,如果无法推断出类型,则默认情况下假定其类型为: Object,例如:

function example<T>(a: any): T {
    return a as T;
}

let test = example(123);
Run Code Online (Sandbox Code Playgroud)

变量test上面的例子中,将类型{}

指定泛型类型或在方法中指定参数类型都是处理此问题的正确方法:

ensure<Person, string>(p => p.firstName);
ensure((p: string) => p.firstName);
Run Code Online (Sandbox Code Playgroud)

您引用的错误是正确的,因为:对于 function 不存在只接受一个泛型的签名ensure

这样做的原因是您可以拥有带有替代签名的函数,这些签名采用不同数量的泛型类型参数:

interface Example {
    ensure<TModel, TValue>(accessor: { (obj: TModel): TValue; }): TValue;
    ensure<TModel>(accessor: { (obj: TModel): any; }): any;
}
interface Person {
    firstName: string;
    lastName: string;
}

let test: Example;

// the method 'ensure' has now 2 overloads:
// one that takes in two generics:
test.ensure<Person, string>((p: Person) => p.firstName);

// one that takes only one generic:
test.ensure<Person>(p => p.firstName);

// when not specified, TypeScript tries to infer which one to use, 
// and ends up using the first one:
test.ensure((p: Person) => p.firstName);
Run Code Online (Sandbox Code Playgroud)

游乐场如上。

如果 TypeScript 没有强制执行签名匹配,它就不知道应该选择哪个签名。

现在回答你问题的另一部分:为什么在没有明确说明泛型的情况下调用函数时p假设为any

一个原因是编译器不能对其可能的类型做出任何假设,TModel不受约束并且可以是任何字面意思,因此类型pany

您可以将通用方法限制为一个接口,如下所示:

ensure<TModel extends Person, TValue>(accessor: { (obj: TModel): TValue; });
Run Code Online (Sandbox Code Playgroud)

现在,如果您在不指定参数类型或泛型类型的情况下调用该函数,它将被正确推断为Person

ensure(p => p.firstName); // p is now Person
Run Code Online (Sandbox Code Playgroud)

希望这完全回答了您的问题。