【问题标题】:"constructor" in typescript interface打字稿界面中的“构造函数”
【发布时间】:2018-04-08 17:38:38
【问题描述】:

我从正在使用的库中收到以下界面:

export interface LatLng {
  constructor(lat: number, lng: number): void;
  lat(): number;
  lng(): number;
}

如何创建此类的实现? (我需要一个测试模拟)一个看起来很自然的实现,构造函数定义为:

export class LatLngImpl implements LatLng {
  constructor(private _lat: number, private _lng: number) {  }

无法编译:

类“LatLngImpl”错误地实现了接口“LatLng”。 属性“构造函数”的类型不兼容。 类型 'Function' 不能分配给类型 '(lat: number, lng: number) => >void'。 类型 'Function' 不匹配签名 '(lat: number, lng: >number): void'

我读过 typescript 中的构造函数接口,但我认为它不适用于这里。

编辑:

我不明白的是界面中的这个constructor() 声明。带有构造函数签名的接口使用new () 语法。

【问题讨论】:

  • 更正式地说,实现接口的类是类实例所具有的契约。由于类的实例不包含构造签名,因此它不能满足接口。 – 瑞安卡瓦诺stackoverflow.com/questions/13407036/…
  • 是的,我了解 `new () : MyInterface` 的工作原理。但是在那个库中,它被声明为“constructor(): void”。我怀疑这是某种错误。接口中的声明将其视为普通函数,而在类中,构造函数是保留字。

标签: typescript


【解决方案1】:

这个库来自哪里?不管是谁写的,都应该受到严厉的谈话。要么是错误(在这种情况下,他们从未测试过),要么是故意的,但是 eeeeevil:使用 not-quite-reserved word constructor 作为实例方法的标识符只是自找麻烦。

编辑 2019-04-25: 麻烦不仅仅是“这是个坏主意”;它实际上看起来就像 JavaScript 原生支持类字段 it is going to be an error to have a class with a property named "constructor"。 TypeScript 将在 3.5+ 版本中更改以反映这一点,因此下面的实现将停止工作。有关更多信息,请参阅最近打开的 GitHub 问题 Microsoft/TypeScript#31020。在 TS3.5 之后,似乎没有办法让一个类的实例 constructor 属性不是实际的构造函数本身。

库的作者要么打算引用你用new 调用的真实构造函数,在这种情况下他们应该做类似@Series0ne 的suggestion 的事情; 或者作者真的想用实例方法来初始化对象,在这种情况下他们应该帮大家一个忙,使用更传统的方法名称,如init()

在任何一种情况下,任何人都不应该接受给你的接口,当然也没有人应该实现它。

让我们实现它:

export class LatLngImpl implements LatLng {
  private _lat: number;
  private _lng: number;
  lat() {
    return this._lat;
  }
  lng() {
    return this._lng;
  }
  // here's the important part, but see Microsoft/TypeScript#31020
  "constructor"(lat: number, lng: number) { 
    this._lat = lat;
    this._lng = lng;
  }
}

诀窍是使用字符串文字"constructor" 而不是裸词constructor。来自TypeScript spec

字符串字面量可用于给出无效标识符的属性名称

通过使用字符串字面量,我们能够将其声明为实例方法,而不是调用new时调用的静态类构造函数方法,并且编译愉快。

如果我们敢的话,现在我们可以使用它了:

const latLng = new LatLngImpl();
latLng.constructor(47.6391132, -122.1284311); // ? Why not init()?
console.log("Latitude: " + latLng.lat() + ", Longitude: " + latLng.lng());

哎呀,我们不应该敢。

EDIT AGAIN 2019-04-25 上面引用的字符串实现将 not work 从 TypeScript 3.5 开始,对此的正确答案将是“你不能这样做”和“改用真正的构造函数”。

希望有所帮助;祝你好运!

【讨论】:

    【解决方案2】:

    从技术上讲,构造函数是一种特殊的静态函数调用,它返回自身的实例,因此将它作为接口的一部分并没有真正意义,因为接口成员是实例绑定的。

    Typescript 有一些编译器技巧可以使它们静态绑定,并将其用于环境声明。

    在您的情况下,要提供实现,您需要从接口中删除构造函数:

    interface LatLng {
        lat(): number;
        lng(): number;
    }
    
    class LatLngImpl implements LatLng {
        constructor(lat: number, lng: number) {
    
        }
    
        lat(): number {
            return 0;
        }
    
        lng(): number {
            return 0;
        }
    }
    

    如果LatLng在别处实现,您只需提供环境声明:

    interface LatLng {
        lat(): number;
        lng(): number;
    }
    
    interface LatLngConstructor {
        new(lat: number, lng: number): LatLng;
    }
    
    declare var LatLng: LatLngConstructor;
    

    注意LatLngConstructor 定义了一个new(...): LatLng,它描述了构造函数签名。

    【讨论】:

    • 重要的是,interfaces 是关于确保创建什么,而不是它是如何到达那里的。以我的经验,强制构造函数以某种方式操作只会导致更多的痛苦,而没有任何实际的好处。即,任何消耗 LatLngImpl 的东西只关心 latlng 属性,而构造函数的细节应该是无关紧要的。任何更严格的东西可能都是错误代码的标志。
    【解决方案3】:

    我相信你不能将构造函数放在接口中。

    interface IPerson 
    {
        firstName: string;
        lastName: string;
    }
    
    class Person implements IPerson 
    {
        constructor (public firstName: string, public lastName: string) 
        {
            //TODO
        }
    }
    
    var personA: IPerson = new Person('Jane', 'Smith');
    var personB: IPerson = { firstName: 'Jo', lastName: 'Smith' };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-23
      • 1970-01-01
      • 2018-05-22
      • 2017-01-29
      • 2016-09-04
      • 2021-02-02
      • 2018-06-26
      相关资源
      最近更新 更多