Union types
Overview
Union types are a powerful way to express a value that can be one of several types. For example, you might have an API for running a program that takes a commandline as either a string
, a string[]
or a function that returns a string
. You can now write:
interface RunOptions {
program: string;
commandline: string[]|string|(() => string);
}
Assignment to union types works very intuitively — anything you could assign to one of the union type’s members is assignable to the union:
var opts: RunOptions = /* ... */;
opts.commandline = '-hello world'; // OK
opts.commandline = ['-hello', 'world']; // OK
opts.commandline = [42]; // Error, number is not string or string[]
When reading from a union type, you can see any properties that are shared by them:
if (opts.length === 0) { // OK, string and string[] both have 'length' property
console.log("it's empty");
}
Using Type Guards, you can easily work with a variable of a union type:
function formatCommandline(c: string|string[]) {
if (typeof c === 'string') {
return c.trim();
}
else {
return c.join(' ');
}
}
Stricter Generics
With union types able to represent a wide range of type scenarios, we’ve decided to improve the strictness of certain generic calls. Previously, code like this would (surprisingly) compile without error:
function equal<T>(lhs: T, rhs: T): boolean {
return lhs === rhs;
}
// Previously: No error
// New behavior: Error, no best common type between 'string' and 'number'
var e = equal(42, 'hello');
With union types, you can now specify the desired behavior at both the function declaration site and the call site:
// 'choose' function where types must match
function choose1<T>(a: T, b: T): T { return Math.random() > 0.5 ? a : b }
var a = choose1('hello', 42); // Error
var b = choose1<string|number>('hello', 42); // OK
// 'choose' function where types need not match
function choose2<T, U>(a: T, b: U): T|U { return Math.random() > 0.5 ? a : b }
var c = choose2('bar', 'foo'); // OK, c: string
var d = choose2('hello', 42); // OK, d: string|number
Better Type Inference
Union types also allow for better type inference in arrays and other places where you might have multiple kinds of values in a collection:
var x = [1, 'hello']; // x: Array<string|number>
x[0] = 'world'; // OK
x[0] = false; // Error, boolean is not string or number
let
declarations
In JavaScript, var
declarations are “hoisted” to the top of their enclosing scope. This can result in confusing bugs:
console.log(x); // meant to write 'y' here
/* later in the same block */
var x = 'hello';
The new ES6 keyword let
, now supported in TypeScript, declares a variable with more intuitive “block” semantics. A let
variable can only be referred to after its declaration, and is scoped to the syntactic block where it is defined:
if (foo) {
console.log(x); // Error, cannot refer to x before its declaration
let x = 'hello';
}
else {
console.log(x); // Error, x is not declared in this block
}
let
is only available when targeting ECMAScript 6 (--target ES6
).
const
declarations
The other new ES6 declaration type supported in TypeScript is const
. A const
variable may not be assigned to, and must be initialized where it is declared. This is useful for declarations where you don’t want to change the value after its initialization:
const halfPi = Math.PI / 2;
halfPi = 2; // Error, can't assign to a `const`
const
is only available when targeting ECMAScript 6 (--target ES6
).
Template strings
TypeScript now supports ES6 template strings. These are an easy way to embed arbitrary expressions in strings:
var name = "TypeScript";
var greeting = `Hello, ${name}! Your name has ${name.length} characters`;
When compiling to pre-ES6 targets, the string is decomposed:
var name = "TypeScript!";
var greeting = "Hello, " + name + "! Your name has " + name.length + " characters";
Type Guards
A common pattern in JavaScript is to use typeof
or instanceof
to examine the type of an expression at runtime. TypeScript now understands these conditions and will change type inference accordingly when used in an if
block.
Using typeof
to test a variable:
var x: any = /* ... */;
if(typeof x === 'string') {
console.log(x.subtr(1)); // Error, 'subtr' does not exist on 'string'
}
// x is still any here
x.unknown(); // OK
Using typeof
with union types and else
:
var x: string | HTMLElement = /* ... */;
if(typeof x === 'string') {
// x is string here, as shown above
}
else {
// x is HTMLElement here
console.log(x.innerHTML);
}
Using instanceof
with classes and union types:
class Dog { woof() { } }
class Cat { meow() { } }
var pet: Dog|Cat = /* ... */;
if (pet instanceof Dog) {
pet.woof(); // OK
}
else {
pet.woof(); // Error
}
Type Aliases
You can now define an alias for a type using the type
keyword:
type PrimitiveArray = Array<string|number|boolean>;
type MyNumber = number;
type NgScope = ng.IScope;
type Callback = () => void;
Type aliases are exactly the same as their original types; they are simply alternative names.
const enum
(completely inlined enums)
Enums are very useful, but some programs don’t actually need the generated code and would benefit from simply inlining all instances of enum members with their numeric equivalents. The new const enum
declaration works just like a regular enum
for type safety, but erases completely at compile time.
const enum Suit { Clubs, Diamonds, Hearts, Spades }
var d = Suit.Diamonds;
Compiles to exactly:
var d = 1;
TypeScript will also now compute enum values when possible:
enum MyFlags {
None = 0,
Neat = 1,
Cool = 2,
Awesome = 4,
Best = Neat | Cool | Awesome
}
var b = MyFlags.Best; // emits var b = 7;
-noEmitOnError
commandline option
The default behavior for the TypeScript compiler is to still emit .js files if there were type errors (for example, an attempt to assign a string
to a number
). This can be undesirable on build servers or other scenarios where only output from a “clean” build is desired. The new flag noEmitOnError
prevents the compiler from emitting .js code if there were any errors.
This is now the default for MSBuild projects; this allows MSBuild incremental build to work as expected, as outputs are only generated on clean builds.
AMD Module names
By default AMD modules are generated anonymous. This can lead to problems when other tools are used to process the resulting modules like a bundlers (e.g. r.js
).
The new amd-module name
tag allows passing an optional module name to the compiler:
//// [amdModule.ts]
///<amd-module name='NamedModule'/>
export class C {
}
Will result in assigning the name NamedModule
to the module as part of calling the AMD define
:
//// [amdModule.js]
define("NamedModule", ["require", "exports"], function (require, exports) {
var C = (function () {
function C() {
}
return C;
})();
exports.C = C;
});