TypeScript泛型:参数类型推断

时间:2016-07-31 20:06:25

标签: typescript

请考虑以下代码:

meteor --version

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

  

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

为什么p推断为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 而不是any

这里是TypeScript游乐场中代码的 link

2 个答案:

答案 0 :(得分:4)

编辑:TypeScript 3.1的即将推出的功能将允许部分类型参数推断(使您引用的示例有效),有关详细信息,请参阅the pull request

原始回答(适用于TypeScript&lt; 3.1):

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

不幸的是,在使用TypeScript中的通用函数时,无论是全部还是全无 - 您必须提供:

  • 匹配功能签名的所有泛型的类型,或
  • 没有泛型,如果你想让编译器&#34;猜测&#34;与您的通话最匹配的功能签名,同时自动推断类型(如果这种推断完全可能)

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

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

let test = example(123);

above example中的变量test将属于{}类型。

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

ensure<Person, string>(p => p.firstName);
ensure((p: string) => p.firstName);

您引用的错误是正确的,因为:函数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);

Playground上述内容。

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

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

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

您可以将泛型方法约束到接口,如下所示:

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

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

ensure(p => p.firstName); // p is now Person

希望这完全回答你的问题。

答案 1 :(得分:0)

由于我遇到了同样的问题,实际上确实需要一个解决方案,而不仅仅是一个解释,这就是我最终如何做到这一点:

function NewTag<Attributes>(): { <Prototype>(P: Prototype): Prototype & { attributes: Attributes, view(attributes: Attributes): any } } {
    return NewTag_Internal;
}

function NewTag_Internal(p) { // simplified to make it fit
    return <any>{ attributes: Object.create(null), view() { }, __proto__: p }
}

var elm = NewTag<{ id:string }>()({
    log(str: string) {
        console.log(str);
    }
})

elm.log(elm.attributes.id)

每次要使用该函数时都需要一对括号,但在我的情况下,因为它只是为了声明事物(因此在代码中不是很普遍)并且启用完全自动完成=&gt;这是值得的。