Murolem
Murolem

Reputation: 821

How to dynamically specify type in TypeScript generics?

How I should modify generic type parameter U of areaCalculator argument of WithArea function for it to extend the base class of current MixinFactory instance?

type Constructor<T> = new (...args: any[]) => T;

interface IArea {
    readonly area: number;
}

class MixinFactory<T extends Constructor<Shape>> {
    constructor(public Superclass: T) {}

    WithArea(areaCalculator: <U extends Shape & IArea>(ctx: U) => number) {
        return new MixinFactory<T & Constructor<IArea>>(class extends this.Superclass implements IArea {
            get area(): number {
                return areaCalculator(this);
            }
        })
    }
}

class Shape {}

class RectRaw extends Shape {
    constructor(
        public x: number,
        public y: number,
        public w: number,
        public h: number) {
            super();
        }
}

const Rect = new MixinFactory(RectRaw).WithArea(ctx => {
    return ctx.w * ctx.h;
}).Superclass;

const rect = new Rect(10, 10, 20, 20);

console.log(rect.area);

Playground

Upvotes: 3

Views: 151

Answers (1)

kaya3
kaya3

Reputation: 51034

Your areaCalculator function doesn't need to be generic; there is no need for an additional type parameter U, and this type parameter is not exposed by the method get area(): number.

The areaCalculator function only needs to be defined as accepting whatever object the constructor type T returns, so with the InstanceType helper, we can extract that and use it directly in the type annotation:

class MixinFactory<T extends Constructor<Shape>> {
    constructor(public Superclass: T) {}

    WithArea(areaCalculator: (ctx: InstanceType<T> & IArea) => number) {
        return new MixinFactory<T & Constructor<IArea>>(class extends this.Superclass implements IArea {
            get area(): number {
                return areaCalculator(this as InstanceType<T> & IArea);
            }
        })
    }
}

Note that a type assertion this as InstanceType<T> & IArea is needed because Typescript isn't able to figure out that if a class extends this.Superclass of type T then its instances must have type InstanceType<T>.

Playground Link

Upvotes: 3

Related Questions