TypeScript 中的类继承不允许派生类比基类更广泛。根据手册:
TypeScript 强制派生类始终是其基类的子类型。
这意味着extends
您覆盖的基类的类的成员是协变的(因为派生类始终是其基类的子类,或者简单地说,更具体)。考虑以下内容 - 覆盖有效,因为"A"
它是更广泛的 union 的子类型"A" | "B"
:
class A {
static b : Array<{ c: "A" | "B" }> = []
}
class B extends A {
static b : Array<{ c: "A" }> = [] // OK
}
但是,相反的结果会导致可分配性错误,因为被覆盖的成员不是逆变的:
class C {
static b : Array<{ c: "C" }> = []
}
class D extends C {
static b : Array<{ c: "C" | "D" }> = [] // Type '"D"' is not assignable to type '"C"'
}
后一个示例在语义上等同于您的情况:eventName
被声明为string literal type onKeyDown
,这意味着不允许任何和所有扩展类扩展它,因此会出现错误。
您的选择是有限的,但是,有一种解决方法。假设您有以下基类E
:
class E {
constructor(public e : string) {}
static b : Array<{ c: "E" }> = []
static s : number = 42;
}
首先,让我们声明派生类并以某种方式命名它,让它成为FB
:
class FB extends E {
constructor(public f: number) {
super(f.toString());
}
}
到目前为止很简单,对吧?这是多汁的部分:
const F: Omit<typeof FB,"b"> & {
new (...args:ConstructorParameters<typeof FB>): InstanceType<typeof FB>
b: Array<{ c: "E" | "D" }>
} = FB;
有很多东西要解压。通过将声明的派生类分配给变量,我们创建了一个类表达式 const F = FB;
,它使类的静态F
部分能够通过变量的显式类型进行类型化。现在对于类型本身0:
Omit<typeof FB, "b">
确保编译器知道FB
(因此,基类E
)的静态部分存在,除了b
我们稍后将重新定义的成员。
new (...args:ConstructorParameters<typeof FB>): InstanceType<typeof FB>
提醒编译器这F
是一个构造函数,而args:ConstructorParameters
实用InstanceType
程序让我们无需更新派生构造函数类型就可以更改基类。
b: ...
将省略的b
成员读入派生类的静态部分,同时扩展它(注意,由于不涉及类继承,所以没有错误)。
以上所有方法都在编译时修复了b
成员,但我们仍然需要确保静态成员在运行时可用,如下所示(有关详细信息,请参阅 MDN on getOwnPropertyDescriptor
/ defineProperty
):
const descr = Object.getOwnPropertyDescriptor(E, "b")!;
Object.defineProperty(F, "b", {
...descr,
value: [...descr.value, { c: "D" }]
});
最后,让我们检查一切是否按预期工作:
console.log(
new F(42), // FB
new F(42).e, // string
F.b, // { c: "E" | "D"; }[]
F.s // number
);
// at runtime:
// FB: {
// "e": "42",
// "f": 42
// },
// "42",
// [{ "c": "D" }],
// 42
上面有例子的游乐场| 适用于您的情况
0请注意,我们经常必须使用typeof FB
类型查询——如果我们没有提前声明类并选择快捷方式到const F: { ... } = class ...
,我们将无法在显式键入变量时引用类本身(如果我们尝试过,编译器会抱怨循环引用)。