【问题标题】:How to Infer Function Parameter Type如何推断函数参数类型
【发布时间】:2016-04-20 19:32:59
【问题描述】:

我有一个被多次调用的函数。

function foo(parameter) {
    parameter.a;
    parameter.b;
    parameter.c;
    parameter.d;
}

每次调用的参数都不同,但它们都是文字并且具有相同的结构。

const first = { a: 1, b: 2, c: 3, c: 4};
const second = { a: 2, b: 4, c: 6, c: 8};

foo(first);
foo(second);

另外,这些对象有很多属性,因此手动指定参数的类型很繁琐,这导致我的问题:如何推断foo的参数类型?编译器正确推断出firstsecond 的类型,但似乎无法推断出parameter 的类型。

这是我尝试过的。

  1. 您可以将实例放入类型位置。

    foo(parameter: typeof first) {
        parameter.a;
        parameter.b;
        //...
    }
    

    它可以工作并且类型正确,但我想避免使用这种方法,因为函数和文字是在不同的文件中定义的,我不想将文字导入带有函数定义的文件中。

  2. 如果我将函数转换为 lambda,编译器似乎很乐​​意推断类型。它看起来像这样:

    function wrapper<T>(parameter: T, foo: (p: T) => void) {
        foo(parameter);
    }
    
    wrapper(first, parameter => { // parameter is inferred to be `typeof first`.
        parameter.a;
        parameter.b;
        //...
    })
    

    但是,一旦我将 labmda 提取到一个变量中(我需要重用该函数),它就会被推断为 any。

    function wrapper<T>(parameter: T, foo: (p: T) => void) {
        foo(parameter);
    }
    const lambda = parameter => { // parameter is `any`
        parameter.a;
        parameter.b;
        //...
    }
    wrapper(first, lambda)
    

【问题讨论】:

  • 我遇到了同样的问题,接口是显而易见的解决方案,但是......因为我们有很多属性并且我们已经编写了一些对象文字......并且编译器推断出类型......可以我们命名该推断类型并使用它来声明其他对象和函数参数的类型?

标签: typescript


【解决方案1】:

我认为接口是您唯一的选择。您可以声明一个具有您感兴趣的属性的虚拟对象,但归根结底这是等效的。

当您创建这些原始对象时,Typescript 正在使用该变量的这些属性构造一个隐式接口。

foo 函数需要在定义时它应该处理的类型信息,以便能够充分进行类型检查。

你的方法:

  1. 这是因为 typeof 首先给参数隐式构造的接口

    {
        a:number;
        b:number;
        c:number;
        d:number;
    }
    

    因此这些字段在函数中可用。

  2. 这出于同样的原因,整个包装函数专门处理第一个参数的类型(在本例中为first 变量),它是相同的隐式接口。在这种情况下,参数被理解为相同类型,因此属性 a、b、c、d 可用。

  3. 解释 lambda 的方式与解释原始函数定义的方式完全相同,参数上没有类型信息。虽然该 lambda 在您的代码中的包装函数中使用,但不能保证 lambda 将以这种方式使用,因此投影类型信息是不安全的。这也是您的功能失败的原因。

现在有一些语言可以处理这个问题。例如,我认为 OCaml 可以根据您正在使用的字段和范围内的内容智能地隐式确定记录变量的类型。然而,Typescript 似乎没有。你必须告诉它你的期望。如果你愿意,你可以使用内联接口,但考虑到你描述你的接口有多长,我怀疑这是值得的。

tl;dr:使用显式接口,它们对你有好处。

【讨论】:

    【解决方案2】:

    界面怎么样?

    interface ILargeObject {
        a: number;
        b: number;
        c: number;
        d: number;
    }
    
    const first: ILargeObject = { a: 1, b: 2, c: 3, d: 4 };
    const second: ILargeObject = { a: 1, b: 2, c: 3, d: 4 };
    
    function foo(parameter: ILargeObject) {
        /* ... */
    }
    

    然后您可以将接口导入到这两个文件中。这解决了您对必须使用函数定义将文字导入文件的担忧。

    一个重要的好处是您的对象类型将在一个地方定义,而不是让函数参数类型依赖于单个对象定义。如果该单个对象在将来的某个时间点发生更改,它将同时破坏您的功能。

    这种方法提供了更高的安全性,而且逻辑清晰。

    【讨论】:

    • 我在问题中提到“这些对象具有非常多的属性,因此手动指定参数的类型很乏味”。而且,我有很多这样的函数,定义接口实际上需要 500 行代码。
    • 抱歉,我不确定如何帮助您。我能想到的唯一另一件事是为每种类型定义一个新变量(例如var firstObjType = typeof first 等)并导出这些变量而不是实际的对象文字。这样您就不必将对象文字导入具有您的函数的文件中。
    猜你喜欢
    • 1970-01-01
    • 2014-05-03
    • 2013-11-10
    • 2020-10-17
    • 2022-01-26
    • 1970-01-01
    • 2019-09-12
    • 2022-01-07
    • 1970-01-01
    相关资源
    最近更新 更多