hero

What's new with TypeScript 4.0?

TypeScript 4.0 just came out, and people probably want to be upgrading to it soon. Here's a quick summary of what's new based on official release notes. This is only meant to be a quick summary, so I have left out edge cases and detailed examples. If you have time, I'd recommended giving the official release note a proper read. But for those who want to get a high-level gist of what changed, this might be useful.

Of course, all credits goes to TypeScript team.

Variadic Tuple Types

TypeScript 4.0 introduced two fundamental changes to improve typing:

  1. Spreads in tuple type syntax can now be generic.
  2. Rest elements can occur anywhere in a tuple, not just at the end.

It is now possible to assign better types to tuples, which validates lengths of the input and the order of the elements.

function concat(arr1, arr2) {
    return [...arr1, ...arr2];
}
`"

```jsx
type Arr = readonly any[];

function concat<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U] {
    return [...arr1, ...arr2];
}

See official release note for more info.

Labeled Tuple Elements

In order to improve readability, it is now possible to provide labels to tuples types.

// ealier versions
type Range = [number, number];

// TypeScript 4.0
type Range = [start: number, end: number];

This could be useful when using tuple in the following situation. In earlier versions, there are no parameter names which can make the code harder to understand.

function foo(...args: Range): void {
    // ...
}

See official release note for more info.

Class Property Inference from Constructors

TypeScript 4.0 now uses control flow analysis to determine the types of properties in classes when noImplicitAny is enabled.

In the following example, TypeScript can work out the type of area based on the assigned type of sideLength and the fact sideLength was used to calculate area.

class Square {
    // Previously: implicit any!
    // Now: inferred to `number`!
    area;
    sideLength;

    constructor(sideLength: number) {
        this.sideLength = sideLength;
        this.area = sideLength ** 2;
    }
}

See official release note for more info.

Short-Circuiting Assignment Operators

Compound assignment operators apply an operator to two arguments and then assign the result to the left side. This is very commonly used in JavaScript and other languages.

// Addition
a += b; // a = a + b

// Multiplication
a *= b; // a = a * b

// Exponentiation
a **= b; // a = a ** b

// Left Bit Shift
a <<= b; // a = a << b

TypeScript 4.0 has introduced three new assignment operators:

  • &&= that substitutes a = a && b
  • ||= that substitutes a = a || b;
  • ??= that substitutes a = a ?? b;

Suppose you haven't seen ?? before (like me). It is called a nullish coalescing operator. It is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined and otherwise returns its left-hand side operand. Here are some examples from MDN.

const foo = null ?? 'default string';
console.log(foo);
// expected output: "default string"

const baz = 0 ?? 42;
console.log(baz);
// expected output: 0

See official release note for more info.

unknown on catch Clause Bindings

From the start TypeScript uses any type for catch variables. Although nothing has been done to the default catch variables. This new version of TypeScript has allowed assignments of unknown type instead of any since unknown is safer.

try {
    // ...
}
catch (x) {
    // x has type 'any' - have fun!
    console.log(x.message);
    console.log(x.toUpperCase());
    x++;
    x.yadda.yadda.yadda();
}

Probably don't need to worry too much about this for now. Just know that there might be more changes coming later which will build on top of this.

See official release note for more info.

Custom JSX Factories

In JSX Fragments allow a component to return several child elements without specifying a wrapper parent element.

const Component = () => (
    <React.Fragment>
        <img src="icon" alt="icon" />
        <button type="submit">Click me</button>
    <React.Fragment>
);

In Typescript 4.0, it is now possible to customise the fragment factory through the new jsxFragmentFactory option in tsconfig.json to apply this change globally.

{
  "compilerOptions": {
    ...
    "jsx": "react",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment"
  }
}

Alternatively, it is possible to only apply to single files using the new /** @jsxFrag */ pragma comment.

/** @jsx h */
/** @jsxFrag Fragment */

import { h, Fragment } from "preact";

let stuff = <>
    <div>Hello</div>
</>;

See official release note for more info.

Speed Improvements in build

TypeScript now cache previous compilation in .tsbuildinfo file, to speed up compilation with --incremental and --noEmitOnError option after a previous compile error.

It is now also possible to use --incremental with --noEmit options and still take advantage of the faster build.

See official release note for more info.

Editor changes

I won't go through them too much here, as they are not likely to affect most people. Here are one sentence explanations:

  • TypeScript's editing support now recognises when a declaration has been marked with a /** @deprecated * JSDoc comment.
  • Visual Studio Code supports selecting different versions of TypeScript.
  • Visual Studio 2017/2019 have the SDK installers above, and MSBuild installs.
  • Speed up Visual Studio Code startup time for large projects with a new mode for editors to provide a partial experience until the full language service experience has loaded up.
  • Smarter auto-imports.
  • There is now a new refactoring option which can convert code to utilise optional chaining.

Breaking Changes

See official release note for more info.

lib.d.ts Changes

Our lib.d.ts declarations have changed – most specifically, types for the DOM have changed.

Properties Overriding Accessors (and vice versa) is an Error

TypeScript now always issues an error when declaring a property in a derived class that would override a getter or setter in the base class.

Operands for delete must be optional

When using the delete operator in strictNullChecks, the operand must now be any, unknown, never, or be optional (in that it contains undefined in the type). Otherwise, use of the delete operator is an error.

interface Thing {
    prop: string;
}

function f(x: Thing) {
    delete x.prop;
    //     ~~~~~~
    // error! The operand of a 'delete' operator must be optional.
}

Usage of TypeScript's Node Factory is Deprecated

TypeScript 4.0 provides a new node factory API, the older "factory" functions for producing AST Nodes are now deprecated.

See official release note for more info.