I want to write a simple entity component system.
class Component {
}
class Entity {
readonly components: Array<Component> = []
}
class T extends Component {}
class F extends Component {
get hello() { return 'world' }
}
class World {
readonly entities: Array<Entity> = []
readonly components: Map<typeof Component, Array<Component>> = new Map()
first_entity(T: typeof Entity) {
return this.entities.find(_ => _ instanceof T)
}
all_entities(T: typeof Entity) {
return this.entities.filter(_ => _ instanceof T)
}
first<T extends Component>(ctor: { new(...args: any[]): T }) {
return this.components.get(ctor)
}
constructor() {
this.components.set(T, [new T()])
this.components.set(F, [new F(), new F()])
console.log(this.first<F>(F))
}
}
let world = new World()
I want to do this:
world.first<FComponent>()
// returns the first instance of an FComponent
// that extends Component.
of course there are instances of bunch of different components, so I want to keep them in a map with a key typeof SubComponent
.
Using a generic type argument with `typeof T`
I've seen these answers and came up with the above code, but it only works if I have to type FComponent twice like this:
world.first<F>(F)
where I want this:
world.first<FComponent>() // returns the first instance of an FComponent that extends Component.
So what I actually want is world.first() to return something of type FComponent so I can dispatch methods on that specific class.
This signature:
first<T extends Component>(ctor: { new(...args: any[]): T }): T
This should type correct:
world.first<F>()?.hello
This shouldn't compile:
world.first<T>()?.hello
If you extract the signature into an overload, then it won't get in the way of the implementation. Even though the definition of first has the signature (ctor: typeof Component) => T
, the external signature is <T extends Component>(ctor: { new (...args: any[]): T }) => T | undefined
.
first<T extends Component>(ctor: { new(...args: any[]): T }): T | undefined;
first(ctor: typeof Component) {
return (this.components.get(ctor) ?? [])[0] // small change to impl
}
Then you'll be able to use it:
new World().first(F)?.hello // OK
// ^? inferred as <F>(ctor: typeof F) => F | undefined