Skip to content
Search

TypeScript:Mixins

lastmod 2022-06-09created 2022-04-1212 edits

Take a constructor

Mixins take a class and extend it with new functionality. So we need to define what is a constructor. Easy as:

// Needed for all mixins
type Constructor<T = {}> = new (...args: any[]) => T;

Knowledge

  • TypeScript (and JavaScript) classes support strict single inheritance only. 1
  • A class can extend another class (e.g. class A {} ; class B extends A {}), but it cannot extend an interface. (e.g. error code: interface A {} ; class B extends A {})
  • In TypeScript, an interface can also extend a class (e.g. class A {} ; interface B extends A {}), but only in a way that involves inheritance.
    • (Important) When an interface extends a class, the interface includes all class members (public and private) but not the class implementation. 2

Implementation

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function applyMixins(derivedConstructor: any, baseConstructors: any[]) {
  baseConstructors.forEach(baseConstructor => {
    Object.getOwnPropertyNames(baseConstructor.prototype).forEach(name => {
      if (name !== 'constructor') {
        Object.defineProperty(
          derivedConstructor.prototype,
          name,
          Object.getOwnPropertyDescriptor(baseConstructor.prototype, name) ||
            Object.create(null),
        );
      }
    });
  });
}

Usage

Used as follows, it works within a single file.

class c0 {
  a: number;

  constructor(a: number) {
    this.a = a;
  }

  get val() {
    return this.a;
  }
}

class c1 extends c0 {
  get first() {
    return this.val + 1;
  }
}

class c2 extends c0 {
  get second() {
    return this.val + 2;
  }
}

class c3 extends c0 {
  b: number;

  constructor() {
    super(300);
    this.b = 200;
  }

  get third() {
    return this.val + 3;
  }
}

interface c3 extends c1, c2 {
  // EMPTY.
}

applyMixins(c3, [c1, c2]);
export default c3;

const temp = new c3();
console.debug(temp.a);
console.debug(temp.first);
console.debug(temp.second);
console.debug(temp.third);
console.debug(temp.b);

Troubleshooting

TS2310: recursively references itself as a base type.

If you export it for use outside the file, the following error is printed.

TS2310: Type 'ReccApi' recursively references itself as a base type.

It is most likely a caching problem. If you are using something like an IDE, try rebooting it.

See also

Favorite site

References

author Zer0 sitemap