Support for Mix-in classes

TypeScript 2.2 adds support for the ECMAScript 2015 mixin class pattern (see MDN Mixin description and “Real” Mixins with JavaScript Classes for more details) as well as rules for combining mixin construct signatures with regular construct signatures in intersection types.

First some terminology:
  • A mixin constructor type refers to a type that has a single construct signature with a single rest argument of type any[] and an object-like return type. For example, given an object-like type X, new (…args: any[]) => X is a mixin constructor type with an instance type X.

  • A mixin class is a class declaration or expression that extends an expression of a type parameter type. The following rules apply to mixin class declarations:

  • The type parameter type of the extends expression must be constrained to a mixin constructor type.

  • The constructor of a mixin class (if any) must have a single rest parameter of type any[] and must use the spread operator to pass those parameters as arguments in a super(…args) call.Given an expression Base of a parametric type T with a constraint X, a mixin class class C extends Base {…} is processed as if Base had type X and the resulting type is the intersection typeof C & T. In other words, a mixin class is represented as an intersection between the mixin class constructor type and the parametric base class constructor type.

When obtaining the construct signatures of an intersection type that contains mixin constructor types, the mixin construct signatures are discarded and their instance types are mixed into the return types of the other construct signatures in the intersection type. For example, the intersection type { new(…args: any[]) => A } & { new(s: string) => B } has a single construct signature new(s: string) => A & B.

Putting all of the above rules together in an example:
  1. class Point {
  2. constructor(public x: number, public y: number) {}
  3. }
  4. class Person {
  5. constructor(public name: string) {}
  6. }
  7. type Constructor<T> = new(...args: any[]) => T;
  8. function Tagged<T extends Constructor<{}>>(Base: T) {
  9. return class extends Base {
  10. _tag: string;
  11. constructor(...args: any[]) {
  12. super(...args);
  13. this._tag = "";
  14. }
  15. }
  16. }
  17. const TaggedPoint = Tagged(Point);
  18. let point = new TaggedPoint(10, 20);
  19. point._tag = "hello";
  20. class Customer extends Tagged(Person) {
  21. accountBalance: number;
  22. }
  23. let customer = new Customer("Joe");
  24. customer._tag = "test";
  25. customer.accountBalance = 0;

Mixin classes can constrain the types of classes they can mix into by specifying a construct signature return type in the constraint for the type parameter. For example, the following WithLocation function implements a subclass factory that adds a getLocation method to any class that satisfies the Point interface (i.e. that has x and y properties of type number).

  1. interface Point {
  2. x: number;
  3. y: number;
  4. }
  5. const WithLocation = <T extends Constructor<Point>>(Base: T) =>
  6. class extends Base {
  7. getLocation(): [number, number] {
  8. return [this.x, this.y];
  9. }
  10. }