接口中的 Typescript 静态方法

Tra*_*r69 15 javascript node.js typescript

我正在寻找一种方法,要求一个类拥有一些静态方法,而不必自己实现它们(就像接口可以定义普通方法一样)。由于接口不支持静态方法,因此以下代码不起作用。

interface MyInterface {
  static fromJSON(json: string): MyInterface
  toJSON(): object
}
Run Code Online (Sandbox Code Playgroud)

抽象类不是我想要的东西,因为它们不需要开发人员自己编写方法,但我必须实现它。
有没有类似的东西,而无需编写大量自定义逻辑?

使用上面的接口,不应接受以下实现:

class MyClass implements MyInterface {
  // Missing static method "fromJSON"
  toJSON() {
    return {}
  }
}
Run Code Online (Sandbox Code Playgroud)

这个也不应该:

class MyClass implements MyInterface {
  static fromJSON(json: string) {
    return 123 // Wrong type
  }

  toJSON() {
    return {}
  }
}
Run Code Online (Sandbox Code Playgroud)

但这一点应该被接受:

class MyClass implements MyInterface {
  static fromJSON(json: string) {
    return new MyClass()
  }

  toJSON() {
    return {}
  }
}
Run Code Online (Sandbox Code Playgroud)

jca*_*alz 23

TypeScript 对于限制类的静态部分确实没有太多支持。这是一个缺失的功能;有关整体功能请求,请参阅microsoft/TypeScript#14600 ;有关“类支持”部分的信息,请参阅 microsoft/TypeScript#33892;有关“支持类成员”部分的信息,请参阅 microsoft/TypeScript#34516 。static implementsabstract static


static对于像您所展示的表单成员这样的东西来说,一个很大的障碍interface是类型系统很难以一种真正能做您想要的事情的方式来理解它。有一个长期悬而未决的问题microsoft/TypeScript#3841,要求 constructor 类的属性应该是强类型的。目前它只有以下类型Function

class Foo {
  instanceProp: string = "i"
  static staticProp: string = "s"
}
const foo = new Foo();
foo.constructor.staticProp; // error!
// -----------> ~~~~~~~~~~ 
// Property 'staticProp' does not exist on type 'Function'
Run Code Online (Sandbox Code Playgroud)

有一些棘手的原因导致这不能轻易完成,在问题中详细说明,但本质上问题是子类构造函数不需要是父类构造函数的真正子类型:

class Bar extends Foo {
  subInstanceProp: string;
  constructor(subInstanceProp: string) {
    super();
    this.subInstanceProp = subInstanceProp;
  }
}
const bar = new Bar("hello");
Run Code Online (Sandbox Code Playgroud)

这里,Bar构造函数的类型为new (subInstanceProp: string) => Bar,它不能分配给Foo构造函数的类型,即new () => Foo。由extends,bar应该可分配给Foo. 但如果bar.constructor不可分配给Foo['constructor'],一切都会中断。

可能有办法解决这个问题,但到目前为止尚未实施。

所有这些意味着无法查看类型的对象MyInterface并确保构造它的对象具有fromJSON方法。因此,拥有static内部interface定义实际上并没有任何有用的方式。


microsoft/TypeScript#33892 和 microsoft/TypeScript#34516 中的请求没有此问题。如果你可以这样写:

class MyClass implements MyInterface static implements MyInterfaceConstructor {
// not valid TS, sorry ------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  toJSON() { return "" };
  static fromJSON(json: string) { return new MyClass() };
}
Run Code Online (Sandbox Code Playgroud)

或这个:

abstract class MyAbstractClass {
  abstract toJSON(): string;
  abstract static fromJSON(json: string): MyAbstractClass
// ------> ~~~~~~
// not valid TS, sorry
}
Run Code Online (Sandbox Code Playgroud)

你有办法做到这一点。遗憾的是,从 TS4.1 开始,这两个功能都尚未实现,因此唯一的方法是使用解决方法。


让我们看看我上面写的MyInterfaceMyInterfaceConstructor接口,看看我们能用它们做什么。现在我们只能通过以下方式约束实例端implements MyInterface

class MyClass implements MyInterface {
  toJSON() { return "" };
  static fromJSON(json: string) { return new MyClass() };
}
Run Code Online (Sandbox Code Playgroud)

我们不能写static implements MyInterfaceConstructor。但我们可以调用一个无操作辅助函数staticImplements并调用它:

function staticImplements<T>(ctor: T) { }

staticImplements<MyInterfaceConstructor>(MyClass); // okay
Run Code Online (Sandbox Code Playgroud)

事实上,编译没有错误就可以保证 的MyClass静态部分是可以接受的。在运行时这是一个空操作,但在编译时这是有价值的信息。让我们看看如果我们做错了会发生什么:

class MyClassBad implements MyInterface {
  toJSON() {
    return ""
  }
}
staticImplements<MyInterfaceConstructor>(MyClassBad); // error!
// ------------------------------------> ~~~~~~~~~~
// Property 'fromJSON' is missing in type 'typeof MyClassBad' 
// but required in type 'MyInterfaceConstructor'.

class MyClassAlsoBad implements MyInterface {
  static fromJSON(json: string) {
    return 123 // Wrong type
  }
  toJSON() {
    return ""
  }
}
staticImplements<MyInterfaceConstructor>(MyClassAlsoBad); // error!
// ------------------------------------> ~~~~~~~~~~~~~~
// The types returned by 'fromJSON(...)' are incompatible between these types.
function validMyClass(ctor: MyInterfaceConstructor) { }
Run Code Online (Sandbox Code Playgroud)

这些就是您正在寻找的错误。是的,静态约束和错误并不完全位于代码中您想要的位置,但至少您可以表达这一点。这是一个解决方法。

此解决方法还有其他版本,可能使用装饰器(在 JS 中的装饰器支持最终确定之前,这种方法已被弃用或搁置),但这是基本思想:尝试将构造函数类型分配给您的构造函数的“静态部分”接口并查看是否有任何故障。

Playground 代码链接