【问题标题】:Is it possible to combine members of multiple types in a TypeScript annotation?是否可以在 TypeScript 注释中组合多种类型的成员?
【发布时间】:2015-02-04 04:36:20
【问题描述】:

似乎我想做的事情是不可能的,但我真的希望它是。

本质上,我有两个接口,我想将单个函数参数注释为它们的组合。

interface ClientRequest {
    userId:     number
    sessionKey: string
}

interface Coords {
    lat:  number
    long: number
}

然后,在函数中,我想做这样的事情:

function(data: ClientRequest&Coords) { ... }

这样我的“数据”对象就可以包含这两种类型的所有成员。

我在 spec preview 的“组合类型的成员”下看到了一些引用,但它似乎还没有出现。

如果不可能,我的解决方案可能如下所示:

interface ClientRequest<T> {
    userId:     number
    sessionKey: string
    data?:       T
}

function(data: ClientRequest<Coords>) { ... }

这在这种情况下会起作用,尽管它不像我想要的那样动态。我真的很希望能够在注释本身中组合多种(2+)类型:

function(data: TypeA&TypeB&TypeC) { ... }

我猜想传统的解决方案是定义一个扩展这些类型的类型,尽管这看起来不太灵活。如果我想添加一个类型,我必须要么

  • (a) 返回声明并重写它,或者
  • (b) 创建一个全新的界面。不确定我是否同意额外的开销。

有没有 TypeScript 专家愿意为我指明正确的方向?

【问题讨论】:

  • 我试图通过定义interface Both&lt;A,B&gt; extends A, B {} 来启用此功能,但被告知“一个接口只能扩展一个类或另一个接口”。 Looks like a dead end.

标签: javascript typescript interface


【解决方案1】:

您的问题的具体答案是:不,没有一个内联注释来表示组合或扩展类型。

对于您要解决的问题,最佳做法是创建第三种类型来扩展其他两种类型。

interface IClientRequestAndCoords extends IClientRequest, ICoords {} 

function(data: IClientRequestAndCoords) 

更新 2018-10-30

TypeScript 现在有类型交集。所以你现在可以简单地做:

interface ClientRequest {
  userId:     number
  sessionKey: string
}

interface Coords {
  lat:  number
  long: number
}

function log(data: ClientRequest & Coords) { 
  console.log(
    data.userId,
    data.sessionKey,
    data.lat,
    data.long
  );
}

【讨论】:

    【解决方案2】:

    如果你使用 ES6 Object.assign 是很有可能的。假设您有这些类型的现有对象。

    首先让我们定义类型

    interface ClientRequest {
        userId:     number
        sessionKey: string
    }
    
    interface Coords {
        lat:  number
        long: number
    }
    

    现在两者的结合:

    type Combined = ClientRequest & Coords;
    

    假设您有两个想要传递给函数的现有对象:

    const foo: ClientRequest = {
        userId: 10,
        sessionKey: "gjk23h872ty3h82g2ghfp928ge"
    }
    
    const bar: Coords = {
        lat: -23,
        long: 52
    }
    

    你可以这样组合它们:

    const myData: Combined = Object.assign({}, foo, bar);
    

    或者像这样创建一个新的:

    const myData: Combined = {
        userId: 10,
        sessionKey: "gjk23h872ty3h82g2ghfp928ge",
        lat: -23,
        long: 52,
    }
    

    上一个方法

    不是类型安全的。

    &lt;Type&gt; {...} 语法将对象转换为尖括号 (Type) 中指定的类型,从而绕过 Typescript 的检查器。见Type Assertion

    const myData = Object.assign({},
        <ClientRequest> {
            userId: 10,
            sessionKey: "gjk23h872ty3h82g2ghfp928ge"
        },
        <Coords> {
            lat: -23,
            long: 52
        }
    );
    

    最后调用函数:

    function myFunc(data: Combined) { ... }
    myFunc(myData);
    

    查看其他问题以了解更多方法:

    How can I merge properties of two JavaScript objects dynamically?

    【讨论】:

    • 你也可以将ClientRequest &amp; Coords 内联,像这样:function myFunc(data: ClientRequest &amp; Coords) {}
    • 第二个代码块到底发生了什么?它看起来像一个类型断言,但在这种情况下不起作用。是不是模板化对象构造的一种形式>
    • @LouisGarczynski 实际上是Type Assertion。我不推荐它,所以我更新了答案
    【解决方案3】:

    接口答案是组合这两种结构的一种相当优雅的方法,但您提到您想知道是否可以将类型组合为注释的一部分。

    关于接口的说明

    我已经提供了与您的问题相关的一些功能的一些描述,但首先我要说的是,如果您因为认为您必须创建一个 ICoords 接口(如在您的质疑它看起来更像一个类) - 放轻松 - 因为接口也可以扩展一个类:

    // Interface extending an interface and a class
    interface IClientRequestAndCoords extends IClientRequest, Coords {} 
    

    界面甚至会合并属性,只要它们具有相同的名称和类型。 (例如,如果他们都声明了一个属性x: string

    以下是您提到的其他注释功能的注释。

    联合类型

    你可能读过的规范是联合类型,它看起来像这样:

    var x: IClientRequest | Coords;
    

    但这只能确保x 是其中之一,而不是两者的组合。据我所知,您的合并类型 IClientRequest &amp; Coords 的语法不在路线图上。

    function go(data: IClientRequest | Coords) {
        var a = data[0]; // IClientRequest
        var b = data[1]; // Coords
    }
    
    // Allowed (even though it doesn't supply Coords data
    go(myClientRequest);
    
    // Allowed (even though it doesn't supply IClientRequest data
    go (myCoords);
    

    这也不是当前版本的一部分,但稍后会发布。

    元组类型

    您可能已经看到的规范的另一个可能部分是元组类型:

    var x: [IClientRequest, Coords];
    

    但这会将数据的形状从结构更改为数组,其中元素0IClientRequest,元素1Coords

    function go(data: [IClientRequest, Coords]) {
        var a = data[0]; // ClientRequest
        var b = data[1]; // Coords
    }
    
    go([myClientRequest, myCoords]);
    

    Uber-注释

    最后,如果您真的不想创建合并界面,您可以使用 uber-annotation:

    function go(data: { userId:number; sessionKey: string; x: number; y: number; } ) {
    
    }
    

    【讨论】:

    • 这不是提议的联合功能的工作方式。在函数 go(data: IClientRequest | Coords) 的示例中, data 的值将不是数组或元组,而是满足任一接口的对象。然后由函数来解决,即: if (data.hasOwnProperty('userId') { // must be IClientRequest
    • @Martin - 我不能不同意,因为我认为这正是我所说的?! But this only ensures that x is either one or the other, not a combination of the two.
    • 是的。你说的对。我又读了一遍,这就是你所说的 :) 在我之前的阅读中,我把元组上的代码示例误认为是你如何在函数中使用联合的延续。我的错,正如他们在 90 年代所说的那样。
    【解决方案4】:

    我在 spec preview 的“组合类型的成员”下看到了一些引用,但它似乎还没有出现。

    我认为您会对intersection 类型(而不是union)更感兴趣。不同之处在于传入的对象必须支持所有属性,而不是其中一个

    Github 问题:https://github.com/Microsoft/TypeScript/issues/1256#issuecomment-64533287

    【讨论】:

    • 是的。这正是我所希望的。
    【解决方案5】:

    我只需要这样做,以下解决方案对我有用:

    type MyFunction = () => void
    
    interface MyFunctionWithProps extends MyFunction {
      prop1: string,
      prop2: number
    }
    
    // compiles fine
    const MyMashup: MyFunctionWithProps = Object.assign(
      () => {},
      {
        prop1: 'hello',
        prop2: 1234
      }
    )
    

    【讨论】:

      【解决方案6】:

      现在,如果类型 P1 和 P2 扩展对象,您可以使用条件类型执行类似的操作:

      type P1UnionP2 = { [k in (keyof P1 | keyof P2)]: k extends keyof P1 ? P1[k] : k extends keyof P2 ? P2[k] : never }
      

      如果这适用于您的情况,最好的方法是:

      interface P1UnionP2 extends P1, P2 { }
      

      【讨论】:

        猜你喜欢
        • 2013-08-03
        • 2019-05-24
        • 2017-09-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-30
        • 2022-11-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多