Typescript: 添加支持接口以定义静态方法

创建于 2017-01-13  ·  43评论  ·  资料来源: microsoft/TypeScript

TypeScript版本: 2.0.3

interface Foo {
public static myStaticMethod(param1: any);
}

预期行为:
没有错误
实际行为:
不支援

Question

最有用的评论

@aluanhaddad我发现您的代码是显而易见的解决方法。

interface JsonSerializable {
      public static fromJson(obj: any);
      public toJson(): string;
}

您对哪个看得更清楚?

所有43条评论

您想实现什么? 您能详细说明一下情况吗?

我想abstract类就是您想要的。

@mhegazy
例:

interface JsonSerializable {
      public static fromJson(obj: any);
      public toJson(): string;
}

@rozzzly在此示例中, abstract class无效。

我认为这将非常酷! Java在最新版本中添加了此功能。

@Serginho
我认为您可能会发现这很有趣:

interface JsonSerializableStatic<C extends new (...args) => JsonSerializable<C>> {
  fromJson(json: string): JsonSerializable<C>;
}

interface JsonSerializable<C extends new (...args) => any> {
  toJson: () => string;
  constructor: C;
}

interface A extends JsonSerializable<typeof A> { }
class A implements JsonSerializable<typeof A> {

  constructor(readonly id: number, readonly name: string) { }
  toJson() { return JSON.stringify(this); }

  static fromJson(json: string): A {
    const obj = JSON.parse(json);
    return new A(obj.id, obj.name);
  }
}

const a = new A(1, 'Charlize');

const json = a.toJson();

const y = A.fromJson(json);
console.info(a, json, y);
console.info(new a.constructor(1, 'Theron'));
const m = new A.prototype.constructor(1, 'Charlize Theron');
console.info(m);

@aluanhaddad我发现您的代码是显而易见的解决方法。

interface JsonSerializable {
      public static fromJson(obj: any);
      public toJson(): string;
}

您对哪个看得更清楚?

@aluanhaddad我发现您的代码是显而易见的解决方法。

接口JsonSerializable {
公共静态fromJson(obj:any);
公共toJson():字符串;
}
您对哪个看得更清楚?

确实,但是我的解决方案不是解决方法,它是用例的依据。 在逐个类的基础上实现静态反序列化和实例序列化,从而进行封装和类型安全。
您的声明:

interface JsonSerializable {
     public static fromJson(obj: any);
     public toJson(): string;
}

是无关紧要的,因为它基本上是重新声明JSON对象的接口。 那绝对不需要任何方式的静态接口方法。

您正在失去接口的概念。 class A implements JsonSerializable应该让我同时实现这两种方法。 但实际上使我得以实现:

toJson: () => string;
constructor: new (...args) => JsonSerializableStatic<C>;

这不是一个明确的解决方案。

没有技术原因不允许在接口上定义静态方法。

@Serginho我不是在争论这种情况是否理想。 我只是试图说明它可以表达。

@aluanhaddad来吧! 开开心心您是否认为打字稿应该在接口中允许使用静态方法? 这是在Java 8的最新版本中实现的,所以我认为我不是在胡说八道。

@Serginho我不认为它特别适合TypeScript。 接口应定义对象提供的功能。 此功能应该是可重写和可互换的(这就是接口方法是虚拟的原因)。 静态是动态行为/虚拟方法的并行概念。 从设计的角度来看,将两者交织在一起是不合适的。

正如@aluanhaddad所写的那样,TypeScript实际上已经具有一种表达所需内容的机制。 最大的区别是,在这种情况下,您将类视为对象,这在逻辑上仍然保持一致。 从我的角度来看,您提出的方法并不特别适合TypeScript(和JavaScript开发),因为类是黑客/二等公民。 当前流行的模式依赖于鸭形/形状编程,而不是刚性类。

我想,您也可以敞开心mind,尝试不同的编程风格。 世界既不以OOP开头也不是以OOP结尾。 您可能会发现使用函数,简单对象和(在某种程度上)原型感到愉快甚至更好的编程。 这种样式与最初为JavaScript设计的样式更加一致。 附带一提,我同意JS正在慢慢地朝着OOP风格的模式(至少在委员会级别上)发展,但这是因为人们大力推动人们不适应不同的编程范例和开发技术。 对我来说,这是一件令人难过和令人失望的事情。

正如@gcnew所说,接口描述的是单个对象的形状,而不是其类。 但是没有理由我们不能使用接口来描述类本身的形状,因为类也是对象。

import assert = require("assert");

interface JsonSerializableStatic<JsonType, InstanceType extends JsonSerializable<JsonType>> {
    fromJson(obj: JsonType): InstanceType;
}
interface JsonSerializable<JsonType> {
    toJson(): JsonType;
}

interface PointJson { x: number; y: number; }
class Point /*static implements JsonSerializableStatic<PointJson, Point>*/ {
    static fromJson(obj: PointJson): Point {
        return new Point(obj.x, obj.y)
    }

    constructor(readonly x: number, readonly y: number) {}

    toJson(): PointJson {
        return { x: this.x, y: this.y };
    }
}
// Hack for 'static implements'
const _: JsonSerializableStatic<PointJson, Point> = Point;

function testSerialization<JsonType, InstanceType extends JsonSerializable<JsonType>>(cls: JsonSerializableStatic<JsonType, InstanceType>, json: JsonType) {
    const instance: InstanceType = cls.fromJson(json);
    const outJson: JsonType = instance.toJson();
    assert.deepEqual(json, outJson);
}
testSerialization(Point, { x: 1, y: 2 });

不符合toJsonfromJson的签名将导致编译错误。 (不幸的是,错误发生在const _而不是方法处。)

可能相关(因为它处理键入静态方法):#5863。


@aluanhaddad :您的示例有一个错误: JsonSerializableconstructor成员实际上将引用名为constructor的实例属性,当使用new调用时返回JsonSerializableStatic 。 这意味着(new ((new X()).constructor)).fromJson({})必须工作。 它成功编译的原因是interface A extends JsonSerializable<typeof A>声明实现有效,而没有实际检查它。 例如,这编译没有错误:

interface I { m(): void; }
interface A extends I { }
// No compile error
class A implements I { em() {} }

@Serginho不是Java用户,但是它看起来不像该语言允许您为类的静态端定义接口(这意味着实现该接口的类必须实现静态方法才能实现)。 Java允许您使用接口中的主体定义静态方法,其TypeScript等效项为:

interface I { ... }
namespace I {
   export function interfaceStaticMethod() {}
}

如果您想建立一个通用工厂怎么办?

interface Factorizable {
  static factory<U>(str: string): U
}

class Foo {
  private data: string[] = []
  bar<T extends Factorizable>(): T[] {
    return this.data.map(T.factory);
  }
}

class Bar implements Factorizable {
  static factory(str: string): Bar {
    // ...
  }
}

// Usage
var x = new Foo();
var y: Bar[] = x.bar();

我不确定在这里提出的interface语法,但是我知道“使用”语法是我要实现的语法。 这是我在Swift中经常使用的模式(使用protocols ),我认为在TypeScript中会非常好。 尽管不是语言设计者或编译器实现者,但我不确定它是否适合TypeScript的预期方向或是否现实。

该类不必具有implement接口。 您只需定义接口,结构类型检查将在使用站点缓存所有问题,例如:

interface Factorizable<U> {
    factory(str: string): U
}

class Foo {
  private data: string[] = []
  bar<T>(factory: Factorizable<T>): T[] {
    return this.data.map(factory.factory);
  }
}

class Bar {
  static factory(str: string): Bar {
    // ...
  }
}

// Usage
var x = new Foo();
var y = x.bar(Bar); // Bar[]

@mhegazy这是一个相对不错的解决方案。 感谢您提供! 🙏

仍然有两件事让我感到不适(诚然,这两个都不是表演障碍):

  1. 我们仍然无法声明Bar明确实现或符合Factorizable

    • 实际上,我认为这确实不是问题。 因为的确,如果Factorizable接口以不兼容的方式更改,则用法x.bar(Bar)将开始出错,然后您将更正漂移更改。
  2. 对我来说,在作业的右侧声明y的类型仍然是一个认知负担。

    • 更奇怪的是,此语法将启用以下行为: var y: Baz[] = x.bar(Bar) 。 显然,这是一个错误,但是语法使开发人员能够通过在两个位置定义返回类型来过度约束问题。

我们仍然无法声明Bar明确实现或符合Factorizable。

涉及两种类型:1.构造函数(例如,类的静态端),以及2.实例端(调用new )。 将两者混为一类是不正确的。 从理论上讲,您可以拥有implementsstatic implements但实际上,正如您所指出的,这很少使用,并且Implements子句实际上并没有增加多少。 无论您是否具有implements子句,都可以通过任何方式在使用站点进行检查。

对我来说,在作业的右边声明y的类型仍然是一个认知负担。

两者的含义不同, var y = x.bar(Bar)声明一个新变量y ,其类型与x.bar(Bar) ; 其中,以var y: Bar[] = x.bar(Bar)声明类型Bar[]的新变量y验证x.bar(Bar)是否可分配给Bar[]

说了算,这是一个样式问题。 就个人而言,我的建议是除非明确需要,否则不要显式使用类型注释。 让类型流过系统。 但是,我已经看到了代码库,其中的样式指南与此相反,所有内容都有显式的类型注释。

@mhegazy感谢您的讨论/观点。

@ andy-hanson感谢您抽出宝贵时间纠正我。 我在示例中修复了错误。

这也有效,并且在编译时显示错误,而无需任何额外的函数调用:

interface Type<T> {
    new (...args: any[]): T;
}

/* static interface declaration */
interface ComparableStatic<T> extends Type<Comparable<T>> {
    compare(a: T, b: T): number;
}

/* interface declaration */
interface Comparable<T> {
    compare(a: T): number;
}

/* class decorator */
function staticImplements<T>() {
    return (constructor: T) => {}
}

@staticImplements<ComparableStatic<TableCell>>()   /* this statement implements both normal interface & static interface */
class TableCell { /* implements Comparable<TableCell> { */  /* not required. become optional */
    value: number;

    compare(a: TableCell): number {
        return this.value - a.value;
    }

    static compare(a: TableCell, b: TableCell): number {
        return a.value - b.value;
    }
}

讨论的结果是什么?

我遇到了这个问题,我也想使用static interface

interface IDb {
  public static instance: () => Db,
}

从构造函数/类已经具有两个接口(构造函数接口和实例接口)的意义上说,大多数人会忘记已经有_static_接口。

interface MyFoo {
  method(): void;
}

interface MyFooConstructor {
  new (): MyFoo;
  prototype: MyFoo;
  staticMethod(): any;
}

const MyFoo = function MyFoo() {
  this.prop = '';
} as any as MyFooConstructor;

MyFoo.prototype = {
  method() { console.log(this); }
}

MyFoo.staticMethod = function () { /* do something static */ }

如果您不使用_abstract_类,那么您已经具备了能力。

感谢@kitsonk的回复。

您的声明似乎应该起作用,但是对于这种情况来说太冗长了。

我刚刚尝试了_abstract_类,但似乎不支持staticabstract

[ts] 'static' modifier cannot be used with 'abstract' modifier.

@zixia是问题#14600

是的,让我们投票。

有人愿意在这里回答我的问题吗? http :

我认为可以通过在接口上指定静态成员来解决整个问题,如果由于不需要此问题而关闭了该问题,那么我非常想知道如何解决它。

@grantila我已经回答了您的问题。 如本期问题所述,除非您有此处未提及的其他要求,否则可以通过将类视为对象来轻松解决。

@ Enet4我更新了问题,它过于简化。 不幸的是,真正的问题无法通过Object.defineProperty()黑客来解决。 顺便说一句,一个hack。 我想确保不要误拼make -基本上是正确的静态检查。

这是我遇到的一个实际问题,我开始移植到TypeScript的代码现在一直保留为JavaScript,因为我目前无法重写否则需要的大量代码。

我要确保不要拼错拼写-基本上是正确的静态检查。

静态检查只能在这里进行。 但是,如果现在您唯一关心的是要设置正确的属性,则强制转换为制造商类型并从那里设置属性似乎可以解决该问题。

@ Enet4 ,这是一个

像您这样的使用类型转换的解决方案实际上不是类型安全的,并且如果这是解决将类类型作为值使用的情况的唯一方法,那么我们将失去动态语言的许多灵活性。

@grantila在我的辩护中,这值得商C & Maker<T>应该与依赖C其他所有类型保持兼容。

我还尝试描绘出接口中的静态方法可以在这里为您提供帮助的地方,但是我可能会遗漏一些东西。 即使您的界面中有static make?(... args: any[]): self类的东西,也必须在运行时在调用之前检查它是否存在。 如果您想继续进行此讨论,请考虑在其他地方进行以减少噪声。 :slightly_smiling_face:

因此,我们不能在实现相同接口的类中键入check静态工厂方法?

我的用例是:

interface IObject {
    static make(s: string): IObject;
}

class A implements IObject{
    static make(s: string): IObject {
        // Implementation A...
    }
}

class B implements IObject{
    static make(s: string): IObject {
        // Implementation B...
    }
}

A.make("string"); // returns A
B.make("string"); // returns B

我不想为此编写一个新的工厂类。

@ tyteen4a03从该示例中删除IObject ,它将进行编译。 另请参见https://github.com/Microsoft/TypeScript/issues/17545#issuecomment -319422545

@ andy-ms是的,显然可以,但是类型检查的全部要点是..检查类型。 您总是可以使类型安全性降级到足以使每个用例都可以编译的程度,但是这忽略了一个事实,即这是一项功能请求,而且还不是那么疯狂。

是的,显然可以,但是类型检查的全部要点是..检查类型。

这是一个很长很长的线程,它说明类的静态端是如何与实例端分开的接口,而implements指向实例端。 @ andy-ms向@ tyteen4a03指示了如何使代码片段正常工作,因为它是_wrong_,而不是放弃类型检查。

我允许静态方法使用类通用参数的用例是用于mixin类。 我正在建立一个使用注释定义列,数据库等的实体框架,并希望将静态函数混入到我的实体类中,从而可以方便地访问正确类型的存储库。

class RepositoryMixin<T> {
    public static repository(): EntityRepository<T> {
        return new EntityRepository<T>(Object.getPrototypeOf(this));
    }
}

@mixin(RepositoryMixin)
class Entity implements RepositoryMixin<Entity> {
    public id: number;
}

Entity.repository().save(new Entity());

@rmblstrp您可以显示如何在示例中使用建议的功能吗? 最好作为可验证的东西?

@rmblstrp不需要此功能。 实际上,由于您使用的是装饰器,因此实际上可以使用_type_来验证带注释的类是否提供了必需的静态方法。 您不需要两者,而实际上是多余的。

您好,我不想超出话题范围或讨论范围。
但是,由于您引入了不同的编程范例(我们应该使用OOP还是功能性的?),因此,我想专门讨论通常用于创建与db的连接或提供某种服务的静态工厂。
在许多语言(如PHP和Java)中,不赞成使用静态工厂,而倾向于依赖注入。 得益于Symfony和Spring等框架,依赖注入和IOC容器已变得流行。
Typescript有一个很棒的IOC容器,名为InversifyJS。

这些是您可以查看如何处理整个文件的文件。
https://github.com/Deviad/virtual-life/blob/master/models/generic.ts
https://github.com/Deviad/virtual-life/blob/master/service/user.ts
https://github.com/Deviad/virtual-life/blob/master/models/user.ts
https://github.com/Deviad/virtual-life/blob/master/utils/sqldb/client.ts
https://github.com/Deviad/virtual-life/blob/master/bootstrap.ts

我并不是说这是一个完美的解决方案(可能未发现某些场景),但它也可以与React一起使用,已经有一些示例可供参考。

另外,我建议您看一下有关函数式编程的视频,其中介绍了哪个方面更好:https://www.youtube.com/watch?v=e-5obm1G_FY&t=1487s
SPOILER:没有人比这更好,这取决于您要解决的问题。 如果您与用户,教室打交道,教师OOP会更好地使用对象对问题进行建模。
如果您需要一个扫描网站的解析器,则使用返回部分结果等的函数生成器也许可以使函数更有效。

@Deviad @aluanhaddad自从我发表文章以来,我一直在使用InversifyJS,它绝对很棒,而且绝对是更好的选择。 在撰写本文时,我以前是PHP / C#之后才刚开始使用Typescript / Node。 熟悉环境和可用软件包只花了一点时间。

这是什么状态? 为什么您要继续关闭回购中尚未解决的问题?

我认为这是应该用“ wontfix”明确标记该问题的情况之一,因为在接口中选择不使用静态方法是设计使然。

@ enet4 ,我是新来者,但这一点还不清楚。 阅读此问题和其他相关问题后,听起来好像大部分是以下问题:

A.很难
B.我们不喜欢到目前为止所看到的(每种)特定语法。
C.一小部分人根本不认为它应该可行,而宁愿删除当前的奇怪方法。

如果实际上是设计使然,而负责人不希望这样做,则应编写一个公共文档,并将其交叉链接到此线程和其他线程中。 与使我们陷入困境相比,这将节省很多时间。

我们已经在此线程中链接到#14600,这是功能请求所要解决的问题。

此页面是否有帮助?
0 / 5 - 0 等级