我正在寻找一种方法,要求一个类拥有一些静态方法,而不必自己实现它们(就像接口可以定义普通方法一样)。由于接口不支持静态方法,因此以下代码不起作用。
interface MyInterface {
static fromJSON(json: string): MyInterface
toJSON(): object
}
抽象类不是我想要的东西,因为它们不需要开发人员自己编写方法,但我必须实现它。
有没有类似的东西,无需编写大量自定义逻辑?
使用上面的接口,不应接受以下实现:
class MyClass implements MyInterface {
// Missing static method "fromJSON"
toJSON() {
return {}
}
}
这个也不应该:
class MyClass implements MyInterface {
static fromJSON(json: string) {
return 123 // Wrong type
}
toJSON() {
return {}
}
}
但是这个应该被接受:
class MyClass implements MyInterface {
static fromJSON(json: string) {
return new MyClass()
}
toJSON() {
return {}
}
}
TypeScript 中确实没有太多支持约束类的静态部分。 这是一个缺失的功能;请参阅 microsoft/TypeScript#14600 了解整体功能请求,以及 microsoft/TypeScript#33892 仅了解“类支持
static implements
”部分,以及 microsoft/TypeScript#34516 仅了解“支持” abstract 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'
有一些棘手的原因导致这不能轻易完成,在问题中详细说明,但本质上问题是子类构造函数不需要是父类构造函数的真正子类型:
class Bar extends Foo {
subInstanceProp: string;
constructor(subInstanceProp: string) {
super();
this.subInstanceProp = subInstanceProp;
}
}
const bar = new Bar("hello");
这里,
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() };
}
或者这个:
abstract class MyAbstractClass {
abstract toJSON(): string;
abstract static fromJSON(json: string): MyAbstractClass
// ------> ~~~~~~
// not valid TS, sorry
}
你有办法做到这一点。 遗憾的是,从 TS4.1 开始,这些功能都尚未实现,因此唯一的方法是使用解决方法。
让我们看看我上面写的
MyInterface
和 MyInterfaceConstructor
接口,看看我们可以用它们做什么。 现在我们只能通过 implements MyInterface
: 来约束实例侧
class MyClass implements MyInterface {
toJSON() { return "" };
static fromJSON(json: string) { return new MyClass() };
}
我们不能写
static implements MyInterfaceConstructor
。 但是我们可以创建一个名为 staticImplements
的无操作辅助函数并调用它:
function staticImplements<T>(ctor: T) { }
staticImplements<MyInterfaceConstructor>(MyClass); // okay
编译没有错误的事实可以保证
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) { }
这些是您正在寻找的错误。 是的,静态约束和错误并不完全位于代码中您想要的位置,但至少您可以表达这一点。 这是一个解决方法。
此解决方法还有其他版本,可能使用装饰器(在 JS 中的装饰器支持最终确定之前,这种方法已被弃用或搁置),但这是基本思想:尝试将构造函数类型分配给您的界面,看看是否有任何问题。