In TypeScript, is it possible to make the following getComponent
method generic, so that I can drop the as SkinComponent
when I call it?
I think part of the problem here may be that what I am passing in is IComponentConstructor
, but what I want back is IComponent
. So I'm not sure how to represent that using generics.
const skinComponent = gameObject.getComponent(SkinComponent) as SkinComponent;
skinComponent.methodOnlyAvailableOnSkinComponent();
export class GameObject {
components: IComponent[];
public getComponent(
ComponentType: IComponentConstructor
): IComponent | undefined {
return this.components.find(c => c instanceof ComponentType);
}
}
I have tried a few different approaches, e.g. the below, but I always seem to end up with the following error.
public getComponent<T extends IComponent>(
ComponentType: IComponentConstructor<T>
): T | undefined {
return this.components.find(c => c instanceof ComponentType);
}
Type 'IComponent | undefined' is not assignable to type 'T | undefined'.
Type 'IComponent' is not assignable to type 'T'.
'IComponent' is assignable to the constraint of type 'T', but 'T'
could be instantiated with a different subtype of constraint 'IComponent'.
PS: For IComponentConstructor
and IComponent
, please see my previous StackOverflow post: Constructor in interface
CodePudding user response:
array.find()
is not a generic method, and therefore does not narrow the type. This means that array.find()
will always return the member type type of the array.
const strings: string[] = ['a', 'b', 'c']
const a = strings.find(str => str === 'a') // type is string | undefined, and not "a"
So this.components.find()
will return a IComponent
because that's the array's member type. And an IComponent
can't be assumed to be any specific subclass of IComponent
However, if
statements do narrow the type. So I would probably change this to a simple loop and an early return:
public getComponent<T extends IComponent>(
ComponentType: IComponentConstructor<T>
): T | undefined {
for (const c of this.components) {
if (c instanceof ComponentType) return c
}
return
}
Now c
is returned after an if
statement has narrowed that type to T
, and everything else works.
const gameObject = new GameObject()
const skinComponent = gameObject.getComponent(SkinComponent);
skinComponent?.methodOnlyAvailableOnSkinComponent(); // works