1

I'm trying to build a mixin function that optionally expects a super class. The rationale is that we often just build intermediate classes to start off with our mixins. I am quite confident with the following declarations, but they don't work, however:

interface Test {
  readonly __TEST: "test";
  new (...args: any[]): {
    readonly test: "test";
  };
}

function TestMixin<SuperClass extends new (...args: any[]) => any>(
  superClass?: SuperClass
) {
  const defaultClass = class {};
  /* Error: Type 'new (...args: any[]) => any' is not assignable to type 'SuperClass extends undefined ? typeof defaultClass : undefined'.ts(2322) */
  const sc: typeof superClass extends undefined ? typeof defaultClass : undefined = superClass === undefined ? defaultClass : superClass;

  /* Error: Type 'SuperClass extends undefined ? typeof defaultClass : undefined' is not a constructor function type. */
  class T extends sc implements InstanceType<Test> {
    public static readonly __TEST = "test";
    public readonly test = "test";
  }

  return T;
}
1
  • You can't extend typeof defaultClass Nothing extends typeof Commented Mar 12, 2019 at 12:34

2 Answers 2

1

You can't extend a conditional type, Typescripts expects the clause in the extends clause to be a constructor, not any other more complicated type.

I think the simplest solution in this case would be to lie to the compiler with a type assertion:

interface Test {
    readonly __TEST: "test";
    new(...args: any[]): {
        readonly test: "test";
    };
}

function TestMixin<SuperClass extends new (...args: any[]) => any = new () => {}>(
    superClass?: SuperClass
) {
    const defaultClass = class { };
    /* ok */
    const sc = (superClass === undefined ? defaultClass : superClass) as SuperClass;

    /* Ok now */
    class T extends sc implements InstanceType<Test> {
        public static readonly __TEST = "test";
        public readonly test = "test";
    }

    return T;
}

let a = TestMixin();
new a().test;

let b = TestMixin(class {
    constructor(n: number) { console.log("Hi") }
    m() { }
});
new b(1).test;
new b(1).m();

As long as nobody specifies an explicit type parameter to TestMixin and omits the parameter, it should work fine.

Sign up to request clarification or add additional context in comments.

1 Comment

@FlorianBachmann glad to hear, upvotes and mark as answered are also appreciated ;)
0

Find a common denominator — the Constructor type.

interface Constructor<T = any> {
  new (...args: any[]): T;
}
interface Test {
    readonly __TEST: "test";
    new(...args: any[]): {
        readonly test: "test";
    };
}

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

function TestMixin<SuperClass extends new (...args: any[]) => any>(
    superClass?: SuperClass
) {
    const defaultClass: Constructor = superClass || class { };

    return class T extends defaultClass implements InstanceType<Test> {
        public static readonly __TEST = "test";
        public readonly test = "test";
    }
}

3 Comments

Minor problem, this looses constructor arguments of the passed in class. Not that you can't pass them in they are just not checked
You, sir, are correct! I got too focused on getting rid of compiler errors and missed it.
The result of this is no instance of superClass anymore. We'd loose all its member.

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.