Is there a way to define type of Entities the way that autocomplete helps with retrieving information about components, correctly interfering types and keys of components?
I've tried the next solution, but it shows errors, because it can't infer correct type of the property from the property name (it requires to use as SpecificType
to compile):
interface IComponent {
type: string;
}
export const RenderComponentDefaultValue = {
type: "render",
renderValue: true
}
export type RenderComponent = typeof RenderComponentDefaultValue;
export const HealthComponentDefaultValue = {
type: "health",
healthValue: 100
}
export type HealthComponent = typeof HealthComponentDefaultValue;
interface IEntity<ComponentTypes extends IComponent = IComponent> {
id: string;
// I guess the magic should happen here, with complex typings of the components property
components: {
[key in ComponentTypes["type"]]: ComponentTypes
}
}
// TEST
const testEntity: IEntity<RenderComponent | HealthComponent> = {
// initialization code
};
//
testEntity.components.render.renderValue // SHOULD BE OK
testEntity.components.render.not_RenderValue // SHOULD BE ERROR
testEntity.components.health.healthValue // SHOULD BE OK
testEntity.components.health.not_healthValue // SHOULD BE ERROR
CodePudding user response:
We first need to declare the default values with as const
.
export const RenderComponentDefaultValue = {
type: "render",
renderValue: true
} as const
export const HealthComponentDefaultValue = {
type: "health",
healthValue: 100
} as const
Otherwise, the inferred type of type
will be just string
. We need to let the compiler infer the literal type which can be done using as const
.
The mapped type inside of IEntity
needs to be changed a bit too.
interface IEntity<ComponentTypes extends IComponent = IComponent> {
id: string;
components: {
[K in ComponentTypes as K["type"]]: K
}
}
We map over the constituent in ComponentTypes
which means that K
is now the full object type. Since object types can't be used as key names, we remap the key to K["name"]
. The type of each key will be K
itself.
The type should now work as expected.
declare const testEntity: IEntity<RenderComponent | HealthComponent>
testEntity.components.render.renderValue // OK
testEntity.components.render.not_RenderValue // Error: 'not_RenderValue' does not exist on type
testEntity.components.health.healthValue // OK
testEntity.components.health.not_healthValue // Error: 'not_healthValue' does not exist on type