Reputation: 497
Suppose I have these classes
abstract class Animal {
public static getSound(): string {
throw new Error("Abstract animal has no sound");
}
}
class Dog extends Animal {
public static getSound(): string {
return "Woof";
}
}
class Cat extends Animal {
public static getSound(): string {
return "Meow";
}
}
// Other animal classes...
And I want to write a function that takes a generic subclass of Animal, i.e. not instance of subclass, as a parameter and calls the corresponding static method getSound
. This is what I've tried so far.
interface ClassType<T = any> {
new (...args: any[]): T;
}
const getAnimalSound = <T extends Animal>(type: () => ClassType<T>) => {
return type().getSound(); // Property 'getSound' does not exist on type 'ClassType '.
};
console.log(() => Dog); // should print "Woof"
console.log(() => Cat); // should print "Meow"
But I'm getting the compile error Property 'getSound' does not exist on type 'ClassType'
.
The meaning of the error is obvious, I didn't set up the types correctly, . How should I go about doing this, without redesigning the classes?
Thanks all.
Upvotes: 1
Views: 2952
Reputation: 3964
ClassType
should be declared as
type ClassType = typeof Dog | typeof Cat;
And the getAnimalSound
should be transformed into:
const getAnimalSound = (type: ClassType): string => type.getSound();
Now, if we call
console.log(getAnimalSound(Dog));
console.log(getAnimalSound(Cat));
their sounds can be heard.
The issue in the original approach is that the static
methods belong to the class object and they cannot be invoked on an instance, so we need to access the type reference.
In fact, the static "inheritance" in the original approach does not make sense, because these methods are invoked as Animal.getSound()
, Dog.getSound()
etc.
Upvotes: 2
Reputation: 32176
I think perhaps the type for getAnimalSound
is not accurate.
The argument should be a function which returns Animal
constructors, not instances. Looks like you're aware of that, as you've written ClassType<T>
to be a type that represents a constructor for instances of type T
. That is almost right, but the critical thing is ClassType<T>
only has information on the constructor signature, it has lost all the information on the static members of the class! This is what leads to your error.
One little known feature of typescript is that if a type T
represents the type of an instance of a class, then the type typeof T
(as weird as that looks) represents the constructor type, including all static members!
So your type for getAnimalSound
could be simplified:
const getAnimalSound<T extends typeof Animal>(type: () => T) => {
return type().getSound() // No Error!
}
Using that, seems like you will get the desired result!:
console.log(getAnimalSound(() => Dog)) // Logs: "Woof".
console.log(getAnimalSound(() => Cat)) // Logs: "Meow".
Upvotes: 1