如何用不兼容的类型覆盖基 class 的 属性?
How to override a property of a base class with an incompatible type?
我在一些我无法修改的第三方库中声明了 class:
export declare class KeyboardSensor implements SensorInstance {
private props;
constructor(props: KeyboardSensorProps);
private attach;
private handleStart;
private detach;
static activators: {
eventName: "onKeyDown";
handler: (event: React.KeyboardEvent, { keyboardCodes, onActivation, }: KeyboardSensorOptions) => boolean;
}[];
}
我想用从该声明扩展的新 class 以这种方式自定义它:
export class CustomKeyboardSensor extends KeyboardSensor {
public static activators = [
{
eventName: "onKeyDown" as const,
moreCode: "...",
},
{
eventName: "onKeyUp" as const,
moreCode: "...",
},
];
}
我显然收到类型 属性 不兼容的错误:
Types of property 'eventName' are incompatible.
如何为 declare class 定义新的 interface/type 并修改 eventName
类型?
不开心因为这里你说只能onKeyDown
:
eventName: "onKeyDown";
应该是 string
:
eventName: string;
如果您希望它始终以 on
开头,您甚至可以这样做:
eventName: `on${string}`;
TypeScript 中的 Class inheritance 不允许派生 classes 比基础 classes 更广泛。根据手册:
TypeScript enforces that a derived class is always a subtype of its base class.
这意味着 class 的成员 extends
您覆盖的基础 class 是 covariant(因为派生 class 始终是 subclass 或简单地说,更具体)。考虑以下 - 覆盖有效,因为 "A"
是更广泛的联合 "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
,这意味着不允许任何和所有扩展 classes扩大它,因此错误。
您的选择是有限的,但是,有一个 hacky 方法可以解决这个问题。假设你有以下基数 class E
:
class E {
constructor(public e : string) {}
static b : Array<{ c: "E" }> = []
static s : number = 42;
}
首先,让我们声明派生的 class 并以某种方式命名它,让它成为 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;
有很多东西要打开。通过将声明的派生 class 分配给一个变量,我们创建了一个 class expression const F = FB;
,它使 class 的 static 部分能够通过显式输入 F
变量来输入。现在对于类型本身0:
Omit<typeof FB, "b">
确保编译器知道 FB
的静态部分(因此,基础 class E
)存在,除了成员 b
我们稍后将重新定义它。
new (...args:ConstructorParameters<typeof FB>): InstanceType<typeof FB>
提醒编译器 F
是构造函数,而 args:ConstructorParameters
和 InstanceType
实用程序使我们无需更改基础 class更新派生的构造函数类型。
b: ...
将省略的 b
成员重新添加到派生 class 的静态端,同时扩展它(请注意,由于不涉及 class 继承,因此有没有错误)。
以上所有内容都在 compile-time 期间修复了 b
成员,但我们仍然需要确保静态成员在运行时可用,类似这样(请参阅 getOwnPropertyDescriptor
/ defineProperty
上的 MDN详情):
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
游乐场with examples above | applied to your case
0 请注意,我们经常不得不使用 typeof FB
类型的查询——如果我们之前没有声明 class 并选择快捷方式const F: { ... } = class ...
,我们将无法在显式键入变量时引用 class 本身(如果我们尝试,编译器会抱怨循环引用)。
我在一些我无法修改的第三方库中声明了 class:
export declare class KeyboardSensor implements SensorInstance {
private props;
constructor(props: KeyboardSensorProps);
private attach;
private handleStart;
private detach;
static activators: {
eventName: "onKeyDown";
handler: (event: React.KeyboardEvent, { keyboardCodes, onActivation, }: KeyboardSensorOptions) => boolean;
}[];
}
我想用从该声明扩展的新 class 以这种方式自定义它:
export class CustomKeyboardSensor extends KeyboardSensor {
public static activators = [
{
eventName: "onKeyDown" as const,
moreCode: "...",
},
{
eventName: "onKeyUp" as const,
moreCode: "...",
},
];
}
我显然收到类型 属性 不兼容的错误:
Types of property 'eventName' are incompatible.
如何为 declare class 定义新的 interface/type 并修改 eventName
类型?
不开心因为这里你说只能onKeyDown
:
eventName: "onKeyDown";
应该是 string
:
eventName: string;
如果您希望它始终以 on
开头,您甚至可以这样做:
eventName: `on${string}`;
Class inheritance 不允许派生 classes 比基础 classes 更广泛。根据手册:
TypeScript enforces that a derived class is always a subtype of its base class.
这意味着 class 的成员 extends
您覆盖的基础 class 是 covariant(因为派生 class 始终是 subclass 或简单地说,更具体)。考虑以下 - 覆盖有效,因为 "A"
是更广泛的联合 "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
,这意味着不允许任何和所有扩展 classes扩大它,因此错误。
您的选择是有限的,但是,有一个 hacky 方法可以解决这个问题。假设你有以下基数 class E
:
class E {
constructor(public e : string) {}
static b : Array<{ c: "E" }> = []
static s : number = 42;
}
首先,让我们声明派生的 class 并以某种方式命名它,让它成为 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;
有很多东西要打开。通过将声明的派生 class 分配给一个变量,我们创建了一个 class expression const F = FB;
,它使 class 的 static 部分能够通过显式输入 F
变量来输入。现在对于类型本身0:
Omit<typeof FB, "b">
确保编译器知道FB
的静态部分(因此,基础 classE
)存在,除了成员b
我们稍后将重新定义它。new (...args:ConstructorParameters<typeof FB>): InstanceType<typeof FB>
提醒编译器F
是构造函数,而args:ConstructorParameters
和InstanceType
实用程序使我们无需更改基础 class更新派生的构造函数类型。b: ...
将省略的b
成员重新添加到派生 class 的静态端,同时扩展它(请注意,由于不涉及 class 继承,因此有没有错误)。
以上所有内容都在 compile-time 期间修复了 b
成员,但我们仍然需要确保静态成员在运行时可用,类似这样(请参阅 getOwnPropertyDescriptor
/ defineProperty
上的 MDN详情):
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
游乐场with examples above | applied to your case
0 请注意,我们经常不得不使用 typeof FB
类型的查询——如果我们之前没有声明 class 并选择快捷方式const F: { ... } = class ...
,我们将无法在显式键入变量时引用 class 本身(如果我们尝试,编译器会抱怨循环引用)。