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. ts
    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. ts
    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. };

object type

TypeScript did not have a type that represents the non-primitive type, i.e. any thing that is not number, string, boolean, symbol, null, or undefined. Enter the new object type.

With object type, APIs like Object.create can be better represented. For example:

  1. ts
    declare function create(o: object | null): void;
  2. create({ prop: 0 }); // OK
  3. create(null); // OK
  4. create(42); // Error
  5. create("string"); // Error
  6. create(false); // Error
  7. create(undefined); // Error

Support for new.target

The new.target meta-property is new syntax introduced in ES2015. When an instance of a constructor is created via new, the value of new.target is set to be a reference to the constructor function initially used to allocate the instance. If a function is called rather than constructed via new, new.target is set to undefined.

new.target comes in handy when Object.setPrototypeOf or __proto__ needs to be set in a class constructor. One such use case is inheriting from Error in NodeJS v4 and higher.

Example
  1. ts
    class CustomError extends Error {
  2. constructor(message?: string) {
  3. super(message); // 'Error' breaks prototype chain here
  4. Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
  5. }
  6. }

This results in the generated JS

  1. js
    var CustomError = (function(_super) {
  2. __extends(CustomError, _super);
  3. function CustomError() {
  4. var _newTarget = this.constructor;
  5. var _this = _super.apply(this, arguments); // 'Error' breaks prototype chain here
  6. _this.__proto__ = _newTarget.prototype; // restore prototype chain
  7. return _this;
  8. }
  9. return CustomError;
  10. })(Error);

new.target also comes in handy for writing constructable functions, for example:

  1. ts
    function f() {
  2. if (new.target) {
  3. /* called via 'new' */
  4. }
  5. }

Which translates to:

  1. js
    function f() {
  2. var _newTarget = this && this instanceof f ? this.constructor : void 0;
  3. if (_newTarget) {
  4. /* called via 'new' */
  5. }
  6. }

Better checking for null/undefined in operands of expressions

TypeScript 2.2 improves checking of nullable operands in expressions. Specifically, these are now flagged as errors:

  • If either operand of a + operator is nullable, and neither operand is of type any or string.
  • If either operand of a -, *, **, /, %, <<, >>, >>>, &, |, or ^ operator is nullable.
  • If either operand of a <, >, <=, >=, or in operator is nullable.
  • If the right operand of an instanceof operator is nullable.
  • If the operand of a +, -, ~, ++, or -- unary operator is nullable.

An operand is considered nullable if the type of the operand is null or undefined or a union type that includes null or undefined. Note that the union type case only only occurs in --strictNullChecks mode because null and undefined disappear from unions in classic type checking mode.

Dotted property for types with string index signatures

Types with a string index signature can be indexed using the [] notation, but were not allowed to use the .. Starting with TypeScript 2.2 using either should be allowed.

  1. ts
    interface StringMap<T> {
  2. [x: string]: T;
  3. }
  4. const map: StringMap<number>;
  5. map["prop1"] = 1;
  6. map.prop2 = 2;

This only apply to types with an explicit string index signature. It is still an error to access unknown properties on a type using . notation.

Support for spread operator on JSX element children

TypeScript 2.2 adds support for using spread on a JSX element children. Please see facebook/jsx#57 for more details.

Example
  1. ts
    function Todo(prop: { key: number; todo: string }) {
  2. return <div>{prop.key.toString() + prop.todo}</div>;
  3. }
  4. function TodoList({ todos }: TodoListProps) {
  5. return (
  6. <div>{...todos.map(todo => <Todo key={todo.id} todo={todo.todo} />)}</div>
  7. );
  8. }
  9. let x: TodoListProps;
  10. <TodoList {...x} />;

New jsx: react-native

React-native build pipeline expects all files to have a .js extensions even if the file contains JSX syntax. The new --jsx value react-native will persevere the JSX syntax in the output file, but give it a .js extension.