Home > other >  Method with generic return type
Method with generic return type

Time:08-01

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

See playground

  • Related