Announcing TypeScript 2.8 RC

Today we’re excited to announce and get some early feedback with TypeScript 2.8’s Release Candidate. To get started with the RC, you can access it through NuGet, or use npm with the following command:

npm install -g typescript@rc

You can also get editor support by

Let’s jump into some highlights that are available in our RC!

Conditional Types

TypeScript 2.8 introduces a new construct called conditional types. This new construct is based on JavaScript’s conditional syntax, and takes the form A extends B ? C : D. You should mentally read this as “if the type A is assignable to B, then the type boils down to C, and otherwise becomes D“.

interface Animal {
    live(): void;
}
interface Dog extends Animal {
    woof(): void;
}

// Has type 'number'
type Foo = Dog extends Animal ? number : string;

// Has type 'string'
type Bar = RegExp extends Dog ? number : string;

Conditional types help model simple choices made over based on types at runtime, but afford significantly more expressive constructs at design-time. For example, imagine the following function written with overloads:

interface Id { id: number, /* other fields */ }
interface Name { name: string, /* other fields */ }

declare function createLabel(id: number): Id;
declare function createLabel(name: string): Name;

These overloads describe a single JavaScript function that makes a choice based on the types of its inputs. If a library has to make the same sort of choice over and over throughout its API, this becomes cumbersome. Here, we can use a conditional type to smush both of our overloads down to one, and create a type alias so that we can reuse that logic.

type IdOrName<T extends number | string> =
    T extends number ? Id : Name;

declare function createLabel<T extends number | string>(idOrName: T): IdOrName<T>;

let a = createLabel("daniel");      // Name
let b = createLabel(26);            // Id
let c = createLabel("" as any);     // Id | Name
let d = createLabel("" as never);   // never

To go further, we could also write a helper type that flattens array types to their element types, but leaves them alone otherwise:

type Flatten<T> = T extends any[] ? T[number] : T;

Conditional types also provide us with a new way to infer types from the types we compare against using the new infer keyword which introduces a new type variable. For example, we could have written Flatten as follows:

// We also could also have used '(infer U)[]' instead of 'Array<infer U>'
type Flatten<T> = T extends Array<infer U> ? U : T

Similarly, we could write helper functions to get returned types of function types:

type GetReturnedType<T> =
    T extends ((...args: any[]) => infer R) ? R : T;

declare function foo(): number;

type FooReturnType = GetReturnedType<typeof foo>; // number

One last thing to note is that conditional types distribute if the type being checked is a type parameter that ends up being instantiated with a union type. So for example, in the following example, Bar has the type string[] | number:

type Foo<T> = T extends any ? T[] : never

// Has type 'string[] | number[]'.
type Bar = Foo<string | number>

For convenience, TypeScript 2.8 provides several new type aliases in lib.d.ts that use conditional types:

/**
 * Exclude from T those types that are assignable to U
 */
type Exclude<T, U> = T extends U ? never : T;

/**
 * Extract from T those types that are assignable to U
 */
type Extract<T, U> = T extends U ? T : never;

/**
 * Exclude null and undefined from T
 */
type NonNullable<T> = T extends null | undefined ? never : T;

/**
 * Obtain the return type of a function type
 */
type ReturnType<T extends (...args: any[]) => any> =
    T extends (...args: any[]) => infer R ? R : any;

JSX Pragmas

TypeScript now supports a pragma comment for specifying the source of a JSX factory within any file. If you’ve used Babel, the syntax is the same, but if not, here’s an example of what it looks like:

/** @jsx dom */
import { dom } from "./renderer"
<h></h>

The above sample imports a function named dom, and uses a JSX pragma to select dom as the factory for all JSX expressions in the file. TypeScript 2.8 will rewrite it to the following when compiling to CommonJS and ES5:

var renderer_1 = require("./renderer");
renderer_1.dom("h", null);

This feature be useful if your project mixes different libraries that support a React-like element factory (e.g. React, Preact, Stencil, Inferno, Cycle, or even others).

Granular Control on Mapped Type Modifiers

TypeScript’s mapped object types provide the capability of saying every property in a resulting type is read-only or potentially optional by adding the readonly or ? modifiers as appropriate:

// Creates a type with all the properties in T,
// but marked both readonly and optional.
type AllModifiers<T> = {
    readonly [P in keyof T]?: T[P]
}

This is handy, but until now, mapped types could only add modifiers if they weren’t previously present. For homomorphic mapped types (which copy all modifiers from the original type), this can be limiting:

interface Foo {
    readonly abc: number;
    def?: string;
}

type Props<T> = {
    [P in keyof T]: T[P]
}

// All modifiers are copied over.
// 'abc' is read-only, and 'def' is optional.
type IdenticalFoo = Props<Foo>

TypeScript 2.8 allows more explicit syntax for adding and removing modifiers in mapped types with the + and - operators. For example,

type Mutable<T> = {
    -readonly [P in keyof T]: T[P]
}

interface Foo {
    readonly abc: number;
    def?: string;
}

// 'abc' is no longer read-only, but 'def' is still optional.
type TotallyMutableFoo = Mutable<Foo>

In the above, Mutable removes readonly from each property of the type that it maps over. Similarly, TypeScript now provides a new Required type in lib.d.ts that removes optionality from each property:

/**
 * Make all properties in T required
 */
type Required<T> = {
    [P in keyof T]-?: T[P];
}

JSX.Element is resolved via the JSX Factory

Currently, when TypeScript uses JSX, it looks up a global JSX namespace to look up certain types (e.g. “what’s the type of a JSX component?”). In TypeScript 2.8, the compiler will try to look up the JSX namespace based on the location of your JSX factory. For example, if your JSX factory is React.createElement, TypeScript will try to first resolve React.JSX.Element, and then resolve JSX.Element within the current scope.

This can be helpful when mixing and matching different libraries (e.g. React and Preact) or different versions of a specific library (e.g. React 14 and React 16), as placing the JSX namespace in the global scope can cause issues. Going forward, we recommend that new JSX-oriented libraries avoid placing JSX in the global scope, and instead export it from the same location as the respective factory function.

Breaking changes

Unused type parameters are checked under --noUnusedParameters

Unused type parameters were previously reported under –noUnusedLocals, but are now instead reported under --noUnusedParameters. You can read more on issue #20568

HTMLObjectElement no longer has an alt attribute

Such behavior is not covered by the WHATWG standard. You can read more on issue #21386

What’s next?

We try to keep our plans easily discoverable on on the TypeScript roadmap for everything else that’s coming in 2.8 and beyond. TypeScript 2.8 proper should arrive towards the end of the month, but to make that successful, we need all the help we can get, so download the RC today and let us know what you think!

Feel free to drop us a line on GitHub if you run into any problems, and let others know how you feel about this RC on Twitter and in the comments below!