I have this declared class in some third party library that I can't modify:
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;
}[];
}
And I want to customize it in this way with new class extending from that declaration:
export class CustomKeyboardSensor extends KeyboardSensor {
public static activators = [
{
eventName: "onKeyDown" as const,
moreCode: "...",
},
{
eventName: "onKeyUp" as const,
moreCode: "...",
},
];
}
I am obviously getting an error that the type property is incompatible:
Types of property 'eventName' are incompatible.
How can I define new interface/type for the declare class where I can modify the eventName
type?
Class inheritance in TypeScript does not allow derived classes to be broader than base classes. As per the Handbook:
This means that members of the class that
extends
the base class that you override are covariant (as derived class is always a subclass of its base or, put simply, is more specific). Consider the following - the override works because"A"
is a subtype of a broader union"A" | "B"
:However, the opposite results in an assignability error because the overridden members are not contravariant:
The latter example is semantically equivalent to your case:
eventName
is declared to be a string literal typeonKeyDown
, meaning any and all extending classes are not allowed broaden it, hence the error.Your options are limited, however, there is a hacky way to go around that. Suppose you have the following base class
E
:First, let's declare the derived class and name it somehow, let it be
FB
:Pretty simple so far, right? Here comes the juicy part:
There is a lot to unpack. By assigning the declared derived class to a variable, we create a class expression
const F = FB;
which enables the static part of the class to be typed via explicit typing of theF
variable. Now for the type itself0:Omit<typeof FB, "b">
ensures the compiler knows the static side ofFB
(and, consequently, the base classE
) is present except for the memberb
which we will be redefining later.new (...args:ConstructorParameters<typeof FB>): InstanceType<typeof FB>
reminds the compiler thatF
is a constructor, whereasargs:ConstructorParameters
andInstanceType
utilities free us to change the base class without the need to update the derived constructor type.b: ...
readds the omittedb
member to the static side of the derived class while broadening it (note that as class inheritance is not involved, there is no error).All the above fixes the
b
member during compile-time, but we still need to make sure the static member is available at runtime with something like this (see MDN ongetOwnPropertyDescriptor
/defineProperty
for details):Finally, let's check if everything works as expected:
Playground with examples above | applied to your case
0 Note that we often have to use the
typeof FB
type query — if we haven't declared the class earlier and opted to shortcut toconst F: { ... } = class ...
, we would not be able to refer to the class itself when explicitly typing the variable (if we tried, the compiler would complain of a circular reference).