3

I have a hierarchy of class mixins that work in plain JavaScript.

const
  AsFoo = ( superclass ) => class extends superclass {
    get foo(){ return true; }
  },

  AsFooBar = ( superclass ) => class extends AsFoo( superclass ){
    get bar(){ return true; }
  },

  FooBar = AsFooBar( Object ),
  fb = new FooBar();

console.log( fb.foo, fb.bar );
// true, true

However when I translate them to TypeScript, I get an error with AsFoo( superclass ).

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

interface Foo {
    foo: boolean;
}

interface FooBar extends Foo {
    bar: boolean;
}

const
  AsFoo = <T extends Constructor>( superclass: T ): Constructor<Foo> & T => class extends superclass implements Foo {
    get foo(){ return true; }
  },
  AsFooBar = <T extends Constructor>( superclass: T ): Constructor<FooBar> & T => class extends AsFoo<T>( superclass ) implements FooBar {
    get bar(){ return true; }
  };

// Type 'Constructor<Foo> & T' is not a constructor function type. ts(2507)

Is there something I can do to make TypeScript work with this pattern? I'd rather not just // @ts-ignore: ¯\_(ツ)_/¯ it.

I am currently using TypeScript 3.2.4.

4
  • Related github.com/Microsoft/TypeScript/issues/4890 Commented Apr 2, 2019 at 2:51
  • Also related: github.com/Microsoft/TypeScript/pull/13743 Commented Apr 2, 2019 at 3:01
  • Ultimately the problem is that typescript in AsFooBar won't like that you are extending from a generic type parameter. Mixins are a special exception to this rule and they are only allowed in a specific scenario. I would go with something like the answer below.. Commented Apr 2, 2019 at 7:46
  • @ShaunLuttin, thanks for the links. I'll check them out. Commented Apr 2, 2019 at 13:01

1 Answer 1

3
export type Constructor<T = {}> = new (...args: any[]) => T;
/* turns A | B | C into A & B & C */
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
    ? I
    : never;
/* merges constructor types - self explanitory */
export type MergeConstructorTypes<T extends Array<Constructor<any>>> = UnionToIntersection<InstanceType<T[number]>>;

export function Mixin<T extends Array<Constructor<any>>>(constructors: T): Constructor<MergeConstructorTypes<T>> {
    const cls = class {
        state = {};

        constructor() {
            constructors.forEach((c: any) => {
                const oldState = this.state;
                c.apply(this);
                this.state = Object.assign({}, this.state, oldState);
            });
        }
    };
    constructors.forEach((c: any) => {
        Object.assign(cls.prototype, c.prototype);
    });
    return cls as any;
}

this is an implementation i was playing around with awhile back, it also merges the states of each class but feel free to alter that part to suit your needs.

The usage is as follows...

class A {
    getName() {
        return "hello"
    }
}


class B {
    getClassBName() {
        return "class B name"
    }
}

class CombineAB extends Mixin([A, B]) {
    testMethod() {
        this.getClassBName //is here
        this.getName // is here
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.