【问题标题】:TypeScript generics: argument type inferenceTypeScript 泛型:参数类型推断
【发布时间】:2016-12-05 21:23:49
【问题描述】:

考虑以下代码:

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

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

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

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

这是 TypeScript 游乐场中代码的 link

【问题讨论】:

  • 附注:您可以为函数类型执行此操作:accessor: (obj: TModel) =&gt; TValue
  • @JeremyDanyow TypeScript 3.1 即将推出的功能将允许部分类型参数推断(使您引用的示例有效),请参阅拉取请求以获取更多详细信息:github.com/Microsoft/TypeScript/pull/23696

标签: typescript


【解决方案1】:

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

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

原答案(适用于TypeScript

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

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

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

请注意,如果无法推断类型,则默认假定其类型为: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

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

【讨论】:

    【解决方案2】:

    因为我遇到了同样的问题并且确实需要一个解决方案而不仅仅是解释,所以我最终是这样做的:

    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)
    

    每次您想使用该函数时都会花费一对括号,但在我的情况下,因为它只是声明事物(因此在代码中不是很普遍)并启用完全自动完成 => 权衡是值得的服用。

    【讨论】:

      猜你喜欢
      • 2020-03-11
      • 1970-01-01
      • 1970-01-01
      • 2019-09-17
      • 2016-11-01
      • 2017-10-04
      • 2020-01-23
      • 1970-01-01
      • 2020-09-09
      相关资源
      最近更新 更多