TypeScript

 

 

 

a list of changes

from 1.8 to 2.5 version

with short examples

2017-09-19

Presentation agenda

Update developers on language changes to improve their understanding and usage of its features so they can provide better quality code and software solutions

Expected duration 45 min ⏰

Get a useful understanding of new features that can be applied immediately 👨‍💻

Versions after 1.8

...with a lot of new language features

Let's list some of the 🆕 features...

  • improved static analysis (general and generic types)

  • null and undefined strict checking

  • read-only and abstract properties

  • private and protected constructors

  • mapped types

  • object spread operator

  • iterators and generators (async also)

  • generic parameter defaults

  • string Enums

  • dynamic module imports

  • optional catch param

...and more in the next 40ish slides 😊

TypeScript 2.0
...so let's start! 🏃

TS 2.0 --strictNullChecks (compiler option)

The null and undefined values are not in the domain of every type and are only assignable to themselves and any

let x: number;
let y: number | undefined;
let z: number | null | undefined;
// Also inspired by C# now you can use Nullable<T> which is same to: some type | null

x = 1;  // Ok
y = 1;  // Ok
z = 1;  // Ok

x = undefined;  // Error
y = undefined;  // Ok
z = undefined;  // Ok

x = null;  // Error
y = null;  // Error
z = null;  // Ok

x = y;  // Error
x = z;  // Error
y = x;  // Ok

y = z;  // Error
z = x;  // Ok
z = y;  // Ok

TS 2.0 non-null assertion operator

! post-fix expression operator may be used to assert that its operand is non-null and non-undefined in contexts where the type checker is unable to conclude that fact.

// EX1 assert that #myElement exists in the document DOM and innerText can be accessed
document.getElementById("myElement")!.innerText = "test 123";



// EX2
function processEntity(e?: Entity) {
    validateEntity(e);
    let s = e!.name;  // Assert that e is non-null and access name
}

The type checker analyses all possible flows of control in statements and expressions to produce the most specific type possible (the narrowed type) at any given location for a local variable or parameter that is declared to have a union type.

function foo(x: string | number | boolean) {
    if (typeof x === "string") {
        x; // type of x is string here
        x = 1;
        x; // type of x is number here
    }
    x; // type of x is number | boolean here
}

TS 2.0 control flow based type analysis

TS compiler now supports type guards that narrow union types based on tests of a discriminant property and furthermore extend that capability to switch statements.

interface Square {
    kind: "square";
    size: number;
}

interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Square | Circle;

function area(s: Shape) {
    // In the following switch statement, the type of s is narrowed in each case clause
    // according to the value of the discriminant property, thus allowing the other properties
    // of that variant to be accessed without a type assertion.
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "circle": return Math.PI * s.radius * s.radius;
    }
}

TS 2.0 tagged union types

The never type represents the type of values that never occur. Specifically, never is the return type for functions that never return and never is the type of variables under type guards that are never true.

// Function returning never must have unreachable end point
function error(message: string): never {
    throw new Error(message);
}


// Inferred return type is never
function fail() {
    return error("Something failed");
}


// Function returning never must have unreachable end point
function infiniteLoop(): never {
    while (true) {
    }
}

TS 2.0 never type

By default the type of this inside a function is any. Now you can provide an explicit this parameter.
this parameters are fake parameters that come first in the parameter list of a function:

// EX1
function f1(this: void) {
    // make sure `this` is unusable in this standalone function
}

// EX2
interface Error {
    msg: string;
}

function f2(this: Error) {
    console.log(this.msg);
}

TS 2.0 specifying the type of this for functions

--noImplicitThis (compiler flag)

A new flag is also added in TypeScript 2.0 to flag all uses of this in functions without an explicit type annotation.

Optional properties and methods can now be declared in classes, similar to what is already permitted in interfaces.

class Bar {
    a: number;
    b?: number; // it is asserted as number | undefined

    f() {
        return 1;
    } 

    // Body of optional method can be omitted, asserted as (() => number) | undefined
    g?(): number; 

    h?() {
        return 2;
    }
}

TS 2.0 optional class properties

A class with private constructor cannot be instantiated outside the class body, and cannot be extended. A class with protected constructor cannot be instantiated outside the class body, but can be extended.

class Singleton {
    private static instance: Singleton;

    private constructor() { }

    static getInstance() {
        if (!Singleton.instance) {
            Singleton.instance = new Singleton();
        }
        return Singleton.instance;
    }
}

let e = new Singleton(); // Error: constructor of 'Singleton' is private.
let v = Singleton.getInstance();

TS 2.0 private and protected constructors

An abstract class can declare abstract properties and/or accessors.
Any sub class will need to declare the abstract properties or be marked as abstract.
Abstract properties cannot have an initializer.
Abstract accessors cannot have bodies.

 

abstract class Base {
    abstract name: string;
    abstract get value();
    abstract set value(v: number);
}

class Derived extends Base {
    name = "derived";

    value = 1;
}

TS 2.0 abstract properties and accessors

An object literal type is now assignable to a type with an index signature if all known properties in the object literal are assignable to that index signature. This makes it possible to pass a variable that was initialized with an object literal as parameter to a function that expects a map or dictionary:

function httpService(path: string, headers: { [x: string]: string }) { }

const headers = {
    "Content-Type": "application/x-www-form-urlencoded"
};

httpService("", { "Content-Type": "application/x-www-form-urlencoded" });  // Ok
httpService("", headers);  // Now ok, previously wasn't

TS 2.0 implicit index signatures

Unused function parameters and local variables will be marked as errors with these compiler options enabled

import B, { readFile } from "./b";
// ^ Error: `B` declared but never used
readFile();

export function write(message: string, args: string[]) {
    // ^^^^ Error: 'arg' declared but never used.
    console.log(message);
}

//Parameters declaration with names starting with 
//_ are exempt from the unused parameter checking. e.g.:
function returnNull(_a, __) { // OK
    return null;
}

TS 2.0 --noUnusedParameters and --noUnusedLocals compiler options

This has been one common source of duplicate definition errors.

Multiple declaration files defining the same members on interfaces.

TypeScript 2.0 relaxes this constraint and allows duplicate identifiers across blocks, as long as they have identical types.

Within the same block duplicate definitions are still disallowed.

// in somefile.ts

interface Error {
    stack?: string;
}

interface Error {
    code?: string;
    path?: string;
    stack?: string;  // OK, previously was error
}

TS 2.0 allow duplicate identifiers across declarations

  • Glob (the **, ?, *. wildcards for include and exclude properties) support in tsconfig.json
  • Module resolution enhancements: BaseUrl, Path mapping, rootDirs and tracing
    • TypeScript 2.0 provides a set of additional module resolution knops to inform the compiler where to find declarations for a given module.
  • Support for UMD module definitions
  • Including built-in type declarations with --lib (cool thing)
  • Module identifiers allow for .js extension
  • Support ‘target : es5’ with ‘module: es6’
  • Trailing commas in function parameter and argument lists are allowed
  • New --skipLibCheck (compiler option that causes type checking of declaration files (files with extension .d.ts) to be skipped)
  • New --declarationDir (compiler option allows for generating declaration files in a different location than JavaScript files)

TS 2.0 all of the other changes which we won't cover in detail...

TypeScript 2.1

TS 2.1 keyof and Lookup types

In JavaScript it is fairly common to have APIs that expect property names as parameters, but so far it hasn’t been possible to express the type relationships that occur in those APIs.

 

The query keyof T yields the type of permitted property names for T.

// EX1
interface Person {
    name: string;
    age: number;
    location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string

// EX2 type-safe lookups:
function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];  // Inferred type is T[K]
}

let x = { foo: 10, bar: "hello!" };

let foo = getProperty(x, "foo"); // OK -> number
let oops = getProperty(x, "wargarbl"); // Error! "wargarbl" is not "foo" | "bar"

TS 2.1 mapped types (thanks to keyof)

Mapped types are produced by taking a union of literal types, and computing a set of properties for a new object type.

class Person {
    constructor(
        public name: string,
        public age: number
    ) { }
}

type Partial<T> = {
    [P in keyof T]?: T[P]
};

var p = new Person("pero", 15);

var ppErr: Partial<Person> = { 
    name: "jack", 
    surname: "test" // Error! 'surname' does not exist on Person, you can't use undefined props, only leave out existing
};

var ppOK: Partial<Person> = { name: "jack" };

TS 2.1 object spread and rest

Similar to array spread ...[1, 2, 3], spreading an object can be handy to get a shallow copy:

// spread the original object into a new shallow copy
let copy = { ...original };

... or merge different objects into one:

// merge props of foo, bar and baz into merged
let merged = { ...foo, ...bar, ...baz };

Object rests are the dual of object spreads, in that they can extract any extra properties that don’t get picked up when destructuring an element:

let obj = { x: 1, y: 1, z: 1 };
let { z, ...obj1 } = obj;
obj1; // {x: number, y:number};

TS 2.1 downlevel async functions

This feature was supported before TypeScript 2.1, but only when targeting ES6/ES2015. TypeScript 2.1 brings the capability to ES3 and ES5 run-times, meaning you’ll be free to take advantage of it no matter what environment you’re using.

async function fn(): Promise<number> {
    return new Promise<number>((resolve, reject) => {
        setTimeout(resolve.bind(null, 2), 1500);
    });
}

async function fn2(num: number) {
    return await fn() * num;
}

function fn3(num: number) {
    fn2(num).then(num => {
        console.log(`returned number: ${num}`);
    })
}

fn3(10); // 20 after 1.5 sec

Note: first, we need to make sure our run-time has an ECMAScript-compliant Promise available globally. That might involve grabbing a polyfill for Promise, or relying on one that you might have in the run-time that you’re targeting. We also need to make sure that TypeScript knows Promise exists by setting your lib flag to something like "dom", "es2015" or "dom", "es2015.promise", "es5"

TS 2.1 support for external helpers library (tslib)

TypeScript injects a handful of helper functions such as __extends for inheritance, __assign for spread operator in object literals and JSX elements, and __awaiter for async functions.

// tsc --module commonjs --importHelpers a.ts
// .ts source:
const copy = { ...o };

// will transpile into:
var tslib_1 = require("tslib"); // importing the tslib
var copy = tslib_1.__assign({}, exports.o);

Now you can enable the --importHelpers compier option to import the helpers from a separate module

Don't forget to provide the tslib, possibly via npm install -D tslib and setting the "paths" option in you tsconfig.json to point to the tslib.d.ts file

TS 2.1 other less important features

  • untyped imports (import .js modules which do not define .d.ts files)
  • Support for --target ES2016, --target ES2017 and --target ESNext

  • Improved any inference (this is only enabled if --noImplicitAny is set)

  • Better inference for literal types (literal types are always inferred for const variables and readonly properties)

  • Use returned values from super calls as ‘this’ (extending built-ins like Error, Array, and Map may no longer work)

  • Configuration inheritance by using the new extends command inside tsconfing.json documents

  • New --alwaysStrict (adds "use strict"; directive atop every generated file)

TypeScript 2.2

TS 2.2 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.

declare 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

TS 2.2 support for ES6 new.target meta-property

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.

function f() {
  if (new.target) { /* called via 'new' */ }
}

// ...which translates into:
function f() {
  var _newTarget = this && this instanceof f ? this.constructor : void 0;
  if (_newTarget) { /* called via 'new' */ }
}

Short explanation: you can check with the new.target property whether the constructor was call with new or as regular function.

TS 2.2 dotted property access for types with string index signature

Types with a string index signature can be indexed using the [ ] notation, but were not allowed to use the . (dot character).

interface StringMap<T> {
    [x: string]: T;
}

const map: StringMap<number> = { age: 5 };

map["prop1"] = 1; // OK! worked before
map.prop2 = 2; // OK!

map["wontWork1"] = "a"; // Error! a is not assignable to type number
map.wontWork2 = "b"; // Error! b is not assignable to type number

TS 2.2 ES6 mixin classes

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.

class 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";

TS 2.2 other less important features

  • Better checking for null/undefined in operands of expressions
  • Support for spread operator on JSX element children
  • New jsx: react-native

TypeScript 2.3

TS 2.3 iterators and generators

ES2015 introduced Iterator, which is an object that exposes three methods, next, return, and throw, as per the following interface:

interface Iterator<T> {
  next(value?: any): IteratorResult<T>;
  return?(value?: any): IteratorResult<T>;
  throw?(e?: any): IteratorResult<T>;
}

ES2015 also introduced “Generators”, which are functions that can be used to yield partial computation results via the Iterator interface and the yield keyword. Generators can also internally delegate calls to another iterable through yield*. For example:

function* f1() {
    yield 5;
    yield* [6, 7, 8, 9, 10];
}

function* f2() {
  yield 1;
  yield* [2, 3];
  yield 4;
  yield* f1();
}

// using the new for .. of loop which leverages iterators and generators
for (x of f2()) { // results in (separated by new line): 1 2 3 4 5 6 7 8 9 10
    console.log(x); 
}

TS 2.3 async iterators and generators

The Async Iteration introduces an AsyncIterator, which is similar to Iterator. The difference lies in the fact that the next, return, and throw methods of an AsyncIterator return a Promise for the iteration result, rather than the result itself. This allows the caller to enlist in an asynchronous notification for the time at which the AsyncIterator has advanced to the point of yielding a value.

// mind the Promise<>
interface AsyncIterator<T> {
  next(value?: any): Promise<IteratorResult<T>>;
  return?(value?: any): Promise<IteratorResult<T>>;
  throw?(e?: any): Promise<IteratorResult<T>>;
}
async function* g() {
  yield 1;
  await sleep(3000);
  yield* [2, 3];
  yield* (async function *() { // => async arrow fn not supported yet
    await sleep(1000);
    yield 4;
  })();
}

// The new for-await-of Statement
async function f() {
  for await (const x of g()) {
     console.log(x);
  }
}

await f(); // 1, ...waits 3 sec..., 2, 3, ...waits 1 second..., 4

Async generator function example:

TS 2.3 generic parameter defaults

Consider a function that creates a new HTMLElement, calling it with no arguments generates a Div; you can optionally pass a list of children as well. Previously you would have to define it as:

 
declare function create(): Container<HTMLDivElement, HTMLDivElement[]>;
declare function create<T extends HTMLElement>(element: T): Container<T, T[]>;
declare function create<T extends HTMLElement, U extends HTMLElement>(element: T, children: U[]): Container<T, U[]>;
With generic parameter defaults we can reduce it to:
declare function create<T extends HTMLElement = HTMLDivElement, U = T[]>(element?: T, children?: U): Container<T, U>;

TS 2.3 other less important features

  • check errors in .js files with @ts-check flag or compiler option --checkJs
  • New --strict master option (one --strict to rule null checks, implicit any / this and always strict mode)
  • Enhanced --init output (command for initializing a tsconfig.json file)

TypeScript 2.4
almost there...

TS 2.4 dynamic import expressions

Dynamic import expressions are a new feature and part of ECMAScript (under https://github.com/tc39/proposal-dynamic-import) that allows users to asynchronously request a module at any arbitrary point in your program.

// import a module
import { sum } from "someExports"; // exports sum() and IM_A_CONST

(async function importConstDynamically(): Promise<void> {
    const { IM_A_CONST } = await import("someExports");
    sum(5, IM_A_CONST);
})();
define(["require", "exports", "tslib", "someExports"], function (require, exports, tslib_1, someExports_1) {
    "use strict";
    exports.__esModule = true;
    (function importConstDynamically() {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var IM_A_CONST;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4, new Promise(function (resolve_1, reject_1) { require(["someExports"], resolve_1, reject_1); })];
                    case 1:
                        IM_A_CONST = (_a.sent()).IM_A_CONST;
                        someExports_1.sum(5, IM_A_CONST);
                        return [2];
                }
            });
        });
    })();
});

Using AMD modules and --importHelpers this compiles to:

TS 2.4 string enums

TypeScript 2.4 now allows enum members to contain string initializers.

enum Colors {
    Red = "RED",
    Green = "GREEN",
    Blue = "BLUE",
}

The caveat is that string-initialized enums can’t be reverse-mapped to get the original enum member name. In other words, you can’t write Colors["RED"] to get the string "Red".

TS 2.4 improved inference for generics

Return types as inference targets - make inferences for the return type of a call.

function arrayMap<T, U>(f: (x: T) => U): (a: T[]) => U[] {
    return a => a.map(f);
}

const lengths: (a: string[]) => number[] = arrayMap(s => s.length);

// As an example of new errors you might spot as a result:
let x: Promise<string> = new Promise(resolve => {
    resolve(10);
    //      ~~ Error!
});

TS 2.4 improved weak type detection

Any type that contains nothing but a set of all-optional properties is considered to be weak. For example, this Options type is a weak type:

interface Options {
    data?: string,
    timeout?: number,
    maxRetries?: number,
}

// In TypeScript 2.4, it’s now an error to assign anything to a 
// weak type when there’s no overlap in properties:
function sendMessage(options: Options) {
    // ...
}

const opts = {
    payload: "hello world!",
    retryOnFail: true,
}

// Error!
sendMessage(opts);
// No overlap between the type of 'opts' and 'Options' itself.
// Maybe we meant to use 'data'/'maxRetries' instead of 'payload'/'retryOnFail'.

TS 2.4 other less important features

  • Type parameter inference from contextual types
  • Stricter checking for generic function
  • Strict contravariance for callback parameters

TypeScript 2.5
...and that's it

TS 2.5 optional catch clause variables

You can now omit the catch error variable like in the example below:

let input = "...";

try {
    JSON.parse(input);
}
catch {
    // ^ Notice that our `catch` clause doesn't declare a variable.
    console.log("Invalid JSON given\n\n" + input)
}

TS 2.5 extract function in VSCode

Export in C# Visual Studio style functions in TypeScript in VSCode editor

TS 2.5 other less important features

  • Type assertion/cast syntax in checkJs/@ts-check mode
    • You can assert / cast types in comments like so:
      var x = /** @type {SomeType} */ (AnyParenthesizedExpression);
      
      
      
  • Deduplicated and redirected packages (this can reduce the memory and runtime footprint of the compiler and language service by avoiding loading .d.ts files from duplicate packages)
     
  • The --preserveSymlinks compiler flag (n this mode, references to modules and packages (e.g. imports and /// directives) are all resolved relative to the location of the symbolic link file, rather than relative to the path that the symbolic link resolves to. )
     
  • New quick fixes (on right click context menu in VSCode)

What (interesting features) you can start applying immediatelly!

  • optional parameters (start names with underscore '_')
  • optional catch parameter (you can leave it out, e.g. try { /* ...code... */ } catch /* no (Error error) */ {  }
  • ! post-fix null assertion operator
  • intersection types (tagged unions) e.g. switch-case checking with Square | Rectangle types
  • the never type (to indicate that a func will never return - endless loops, errors...)
  • specify the type of this in func
  • private and protected constructors, abstract props
  • mapped types and new integrated type decorators such as: Nullable<T>, Readonly<T>, Partial<T>...
  • object spread (easy shallow copying, merging) and object rest (pick up destructuring leftovers)
  • new object type
  • new.target property in function's body (checks if it was called with or without new i.e. constructor)
  • mixin classes (if you really wish so :-))
  • generators (with iterators)
  • the async - await syntax (which is probably the biggest new feature) if downlevelAsyncFunctions
  • dynamic importing
  • string enums
  • optional catch parameter

K
THX
BYE!

Made with Slides.com