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 asuper(...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
tsclass Point {
constructor(public x: number, public y: number) {}
}
class Person {
constructor(public name: string) {}
}
type Constructor<T> = new (...args: any[]) => T;
function Tagged<T extends Constructor<{}>>(Base: T) {
return class extends Base {
_tag: string;
constructor(...args: any[]) {
super(...args);
this._tag = "";
}
};
}
const TaggedPoint = Tagged(Point);
let point = new TaggedPoint(10, 20);
point._tag = "hello";
class Customer extends Tagged(Person) {
accountBalance: number;
}
let customer = new Customer("Joe");
customer._tag = "test";
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
).
tsinterface Point {
x: number;
y: number;
}
const WithLocation = <T extends Constructor<Point>>(Base: T) =>
class extends Base {
getLocation(): [number, number] {
return [this.x, this.y];
}
};
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:
tsdeclare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error
create(false); // Error
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
tsclass CustomError extends Error {
constructor(message?: string) {
super(message); // 'Error' breaks prototype chain here
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
}
}
This results in the generated JS
jsvar CustomError = (function(_super) {
__extends(CustomError, _super);
function CustomError() {
var _newTarget = this.constructor;
var _this = _super.apply(this, arguments); // 'Error' breaks prototype chain here
_this.__proto__ = _newTarget.prototype; // restore prototype chain
return _this;
}
return CustomError;
})(Error);
new.target
also comes in handy for writing constructable functions, for example:
tsfunction f() {
if (new.target) {
/* called via 'new' */
}
}
Which translates to:
jsfunction f() {
var _newTarget = this && this instanceof f ? this.constructor : void 0;
if (_newTarget) {
/* called via 'new' */
}
}
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 typeany
orstring
. - If either operand of a
-
,*
,**
,/
,%
,<<
,>>
,>>>
,&
,|
, or^
operator is nullable. - If either operand of a
<
,>
,<=
,>=
, orin
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.
tsinterface StringMap<T> {
[x: string]: T;
}
const map: StringMap<number>;
map["prop1"] = 1;
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
tsfunction Todo(prop: { key: number; todo: string }) {
return <div>{prop.key.toString() + prop.todo}</div>;
}
function TodoList({ todos }: TodoListProps) {
return (
<div>{...todos.map(todo => <Todo key={todo.id} todo={todo.todo} />)}</div>
);
}
let x: TodoListProps;
<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.