Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement Pure Class mixins with Typescript

Im trying to define a pure class based mixin function, but I cannot get the type signature right for this one.

The intent is to provide a function that accepts any Class A as parameter and returns a new Class B that extends the original Class A.

export function mixin<A>(myclass: A) {
  return class B extends A {
    newMethod() {
      //stuff
    }
  }
}

As I said, I cannot figure this out.

  • I need a way to express that A needs to be a class.

  • I also need to express the return type, which yields several errors, among:

    error TS4060: Return type of exported function has or is using private name 'B'.

Additional information:

  • This is inside a utils.ts module that is exported so that other modules can use it
  • This is all being worked in the context of a library I am writing
like image 547
franleplant Avatar asked Dec 25 '16 03:12

franleplant


2 Answers

There's an open issue for this one: Allow class to extend from a generic type parameter

For now you can work around that with something like this:

interface Base{}
interface BaseClass<T> {
    new (): T
    readonly prototype: T;
}

function mixin<T extends Base>(baseClass: BaseClass<T>) {
    class B extends (baseClass as BaseClass<Base>) {
        newMethod() { }
    }

    return B as BaseClass<T & B>;
}

(code in playground)

Which is based on code from here: extends dynamic Base class with generic throw an error


Edit

You can define an interface for the methods which will be added by the new class, something like:

interface B {
    newMethod(): void;
}

function mixin<T extends Base>(baseClass: BaseClass<T>): BaseClass<T & B> {
    class BImpl extends (baseClass as BaseClass<Base>) implements B {
        newMethod() {
            console.log("B.newMethod");
        }
    }

    return BImpl as BaseClass<T & B>;
}

(code in playground)

Then you can export the B interface and then you can use it anywhere.
This code works well:

class A implements Base {
    method() {
        console.log("A.method");
    }
}

let NewA = mixin(A);
let newA = new NewA();
newA.method();
newA.newMethod();

Output:

A.method
B.newMethod

like image 129
Nitzan Tomer Avatar answered Sep 29 '22 15:09

Nitzan Tomer


I wanted to use mixins in TypeScript 2.2 with

  • abstract base class
  • decorators on mixins properties (@Parameter)
  • export mixin definition (Feature) and mixin application (Derived) from module
  • generate declaration file ("declaration": true in tsconfig.json)

I started with following code which unfortunately does NOT work:

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

export abstract class AbstractBase {
    fieldA = "a";
}

export function Feature<T extends Constructor<object>>(Base: T) {
    return class extends Base {
        @Parameter()
        fieldB = "b";
    };
}

export class Derived extends Feature(AbstractBase) {
    fieldC = "c";
}

Finally I ended up with following code which is more complicated but works as expected:

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

export abstract class AbstractBase {
    fieldA = "a";
}

export interface Feature {
    fieldB: string;
}

export function Feature<T extends Constructor<object>>(Base: T): T & Constructor<Feature> {
    class TWithFeature extends Base implements Feature {
        @Parameter()
        fieldB = "b";
    }
    return TWithFeature;
}

export const AbstractBaseWithFeature = Feature(AbstractBase as Constructor<AbstractBase>);

export class Derived extends AbstractBaseWithFeature {
    fieldC = "c";
}

In addition to @bruno-grieder answer see following links:

  • abstract constructor type - https://github.com/Microsoft/TypeScript/issues/5843

  • decorators within mixins - https://github.com/Microsoft/TypeScript/issues/14607

  • mixin classes - https://github.com/Microsoft/TypeScript/pull/13743
like image 40
Vojta Avatar answered Sep 29 '22 14:09

Vojta