Reputation: 5094
I'm trying to figure out how I can pass abstract class types as a parameter to a generic method in TypeScript.
I'm working on an Entity-Component system in Typescript. This means I have entities, which are containers of components, and I can fetch any arbitrary component from them. Here's a minimal example:
abstract class Component { }
class PlayerController extends Component { }
class MonsterController extends Component { }
class LaserGun extends Component { }
class Collider extends Component { }
class Movement extends Component { } // etc, you get the idea
class Entity {
public components: Component[] = [];
public fetch<T extends Component>(componentType: { new(): T }): T | undefined {
return this.components.find(c => c instanceof componentType) as T;
}
}
Now I can fetch individual component types like this:
const entity = new Entity();
entity.components.push(new PlayerController());
entity.components.push(new Movement());
entity.components.push(new LaserGun());
const playerController = entity.fetch(PlayerController);
// ^ this variable controller is typed PlayerController,
// so I can use PlayerController-specific features.
However, I want to upgrade this structure by being able to request abstract types. For example, I could have other Weapons beside LaserGun, and just request any Weapon at all that's attached to the class without caring which kind of weapon it is, like this:
abstract class Weapon extends Component {
public abstract attack(): void;
}
class LaserGun extends Weapon {
public attack(): void { /* pew pew */ }
}
class Sword extends Weapon {
public attack(): void { /* swish swish */ }
}
I can't actually fetch this abstract class though, because abstract classes don't have constructors, and TypeScript doesn't let me pass them as a parameter to the above method:
const weapon = entity.fetch(Weapon);
// ~~~~~~
// Argument of type 'typeof Weapon' is not assignable to parameter of type 'new () => Weapon'.
// Cannot assign an abstract constructor type to a non-abstract constructor type.ts(2345)
How can I update my fetch()
method to allow me to pass any class type that extends Component, including abstract classes?
For now my workaround is to simply make Weapon a non-abstract class, but I'd like to know how to do this in a way that lets me use abstract classes.
Upvotes: 1
Views: 1523
Reputation: 2610
You can use abstract construct signatures for that (playground):
class Entity {
public components: Component[] = [];
public fetch<T extends Component>(componentType: abstract new() => T): T | undefined {
return this.components.find(c => c instanceof componentType) as T;
}
}
// no errors
const weapon = entity.fetch(Weapon);
const sword = entity.fetch(Sword);
Upvotes: 2