Variadic Tuple Types

Older versions of Typescript implement a concat method that looks like this:

function concat<T.U> (arr1: T[], arr2: U[]) {
  return [...arr1, ...arr2]
}
Copy the code

This way concat method return value type is a joint types of arrays (T | U) []

declare const arr1: number[]
declare const arr2: string[]

// type: (string | number)[]
const arr = concat(arr1, arr2)
Copy the code

What if you want to concat primitives

declare const arr1: [number.string]
declare const arr2: [boolean]

// type: (string | number | boolean)[]
const arr = concat(arr1, arr2)
Copy the code

The primitive has a specific length and element type. The return type of the above example is obviously imprecise. The expected return type is [number, string, Boolean].

To achieve the desired results, you could only write overloads in older versions of TS

function concat<T.U.V> (arr1: [T, U], arr2: [V]) :T.U.V]
function concat<T.U> (arr1: T[], arr2: U[]) {
  return [...arr1.arr2]}Copy the code

But if the length of the incoming progenitors is uncertain, we have to keep writing overloads to cover all cases as best we can, which is obviously unacceptable.

TypeScript 4.0 brings with it two fundamental changes and improvements in inference.

One change is that the paradigm can be used to extend operators. This means that you can declare a mutable progenitor using a stereotype.

This allows you to implement a type that supports better concat functions

function concat<T extends unknown[].U extends unknown[] > (t: [...T], u: [...U]) : [...T.U] {
    return [...t, ...u];
}

declare const arr1: [string.number]
declare const arr2: string[]
declare const arr3: ['hello']

concat(arr1, arr2) // [string, number, ...string[]]
concat(arr1, arr3) // [string, number, 'hello']
Copy the code

Another change is that rest arguments in older versions of Typescript only support array types and must be placed at the end of a meta-ancestor. Now you can put it anywhere in the original.

// Older versions of ts
type t1 = [...string[]]
type t2 = [...[string.number]] // error: A rest element type must be an array type.
type t3 = [...string[], string] // A rest element must be last in a tuple type
Copy the code

Array types of indefinite length use the extension operator. If they are not placed last, all subsequent elements are inferred as the type of the array element and the combined type of the subsequent element types

type t1 = string[]
type t2 = [boolean. t1,number] // [boolean, ...(string | number)[]]
Copy the code

Labeled Tuple Elements (Labeled Tuple Elements)

If we were to create an icon, we might have the following implementation

function createIcon(url: string, size: [number.number]) {
    const [width, height] = size
    return new BMapGL.Icon(url, new BMapGL.Size(width, height));
}
Copy the code

The size argument is a meta-ancestor type, but when we call createIcon, we only know the type of size, not the meaning of each element. At call time, you also need to jump to the function body to see the meaning of each element of size.

In Typescript 4.0, meta-ancestor elements can be tagged. The createIcon method in the above example can be implemented like this:

function createIcon(url: string, size: [width: number, height: number]) {
    const [width, height] = size
    return new BMapGL.Icon(url, new BMapGL.Size(width, height));
}
Copy the code

This allows you to see the meaning of each element of the size parameter at call time

Some rules of use

When you mark a tuple element, you must also mark all other elements in the tuple.

type Size = [width: number.number] // error: Tuple members must all have names or all not have names.
Copy the code

You don’t need to name variables differently when you deconstruct tags. In the following example, the variable name does not need to use width and height when deconstructing the size parameter

function createIcon(url: string, size: [width: number, height: number]) {
    const [w, h] = size
    return new BMapGL.Icon(url, new BMapGL.Size(w, h));
}
Copy the code

Overloading can be achieved using tagged meta-ancestors

Class Property Inference from Constructors

For example, in older versions of Typescript, when noImplicitAny is enabled, the defined instance properties area and sideLength report errors. Because it does not explicitly declare a type, it is inferred to be any.

class Square { 
    area; 
    sideLength; 
    constructor(sideLength: number) { 
        this.sideLength = sideLength; 
        this.area = sideLength ** 2; }}Copy the code

In Typescript 4.0, the type of this instance attribute is inferred from the constructor function, and both area and sideLength are inferred to be of type number without error.

Typescript cannot infer the type of a class instance property if the initialization is not written in the constructor function.

class Square { 
    // error: Member 'sideLength' implicitly has an 'any' type.
    sideLength; 
    constructor(sideLength: number) { 
        this.initialize(sideLength) 
    } 
    initialize(sideLength: number) { 
        this.sideLength = sideLength; }}Copy the code

Need to display a statement at this time the type of instance attributes, but if strictPropertyInitialization options (check has been declared but not in the constructor sets the class attribute) also need to display the assignment assertions to type system identification

class Square { sideLength! :number; 
    constructor(sideLength: number) { 
        this.initialize(sideLength) 
    } 
    initialize(sideLength: number) { 
        this.sideLength = sideLength; }}Copy the code

Short-circuiting Assignment Operators (Short-circuiting Assignment Operators)

Among the new features in ES2021 is a proposal for Logical Assignment Operators.

When variable A is truthy, set its value to b, which is equivalent to a = a && b

a &&= b;
Copy the code

When the variable a is falsy, set it to b, that is equivalent to a = a | | b

a ||= b;
Copy the code

When variable A is nullish, set it to B, i.e., a = a?? B.

?? The Nullish Coalescing operator is a new feature of ES2020

// set a to b only when a is nullisha ?? = b;Copy the code

Typescript 4.0 supports these features

Catch Clause variables can be declared as unknown (Unknown on catch Clause Bindings).

In older versions of Typescript, variables in a catch clause have type any and cannot be declared as other types

try{}catch(err: unknown) { // error: Catch clause variable cannot have a type annotation.

}
Copy the code

Declaring this variable unknown is supported in Typescript version 4.0, and this example will not fail.

This is done because the any type is compatible with all other types, and as in the example above, any operation on the ERR variable will not report a type error. Unknown is safer than any because it reminds us to do some type checking before we can manipulate the value.

try{}catch(err: unknown) {
    if (err instanceof Error) {
        console.error(err.message)
    }
}
Copy the code

Customize the JSX Fragment factory function

Older versions of Typescript already support custom JSX factory functions, which can be customized with the jsxFactory option.

In Typescript 4.0, you can customize Fragment factory functions with the new jsxFragmentFactory option.

The following tsconfig.json configuration tells TypeScript to convert JSX in a react-compatible way, but to switch each factory function to H instead of react.createElement. And use a Fragment instead of react. Fragment.

Use the following tsconfig.json configuration

{ 
  "compilerOptions": { 
    "target": "esnext"."module": "commonjs"."jsx": "react"."jsxFactory": "h"."jsxFragmentFactory": "Fragment"}}Copy the code

Compile the following code

import { h, Fragment } from "preact"; 
let stuff = <> 
    <div>Hello</div> 
</>; 
Copy the code

The output

"use strict";
exports.__esModule = true;
/ * *@jsx h */
/ * *@jsxFrag Fragment */
var preact_1 = require("preact");
var stuff = preact_1.h(preact_1.Fragment, null,
    preact_1.h("div".null."Hello"));

Copy the code

The JSX factory function supports the /** @jsx */ comment to specify the JSX factory function used by the current file. Also, the Fragment factory function can be specified with the new /** @jsxfrag */ comment.

As follows, specify the JSX factory and Fragment factory functions used in the current file in the header of the file.

The way you specify it through an annotation has a higher priority than the one configured in the tsconfig.json file

/** @jsx h */ 
/** @jsxFrag Fragment */ 
import { h, Fragment } from "preact"; 
let stuff = <> 
    <div>Hello</div> 
</>; 
Copy the code

Major changes

lib.d.ts

Typescript 4.0 removes Document. origin, which only works in older versions of IE, while Safari MDN recommends self.origin instead.

As follows, accessing the Document origin property in Typescript version 4.0 will prompt that the property does not exist

document.origin // error: Property 'origin' does not exist on type 'Document'
Copy the code

If you want to use this property on older versions of IE, you need to set it explicitly

interface Document {
    origin: string
}

console.log(document.origin)
Copy the code

Properties Overriding Accessors (and vice versa) is an Error

In older versions of Typescript, an error is reported only when the useDefineForClassFields option is used when instance attributes of a subclass override accessor attributes of a parent class.

class Base { 
    get foo() { 
        return 100; 
    } 
    set foo(val) { 
        // ... }}class Derived extends Base {
// Older versions of useDefineForClassFields error: 'foo' is defined as an accessor in class 'Base', but is overridden here in 'Derived' as an instance property.
    foo = 10;
} 
Copy the code

In Typescript version 4.0, an error is always reported when instance attributes of a subclass override accessor attributes of the parent class (or when accessor attributes of a subclass override instance attributes of the parent class), with or without the useDefineForClassFields option.

The operation object of delete must be optional

Typescript version 4.0 uses the delete operator when strictNullChecks is enabled, and the operation object must now be any, unknown, never, or optional (because it contains undefined in its type). Otherwise, using the DELETE operator will report an error.

interface Thing { 
    prop: string; 
    a: unknown;
    b: any;
    c: never;
    d: undefined;
} 
function f(x: Thing) { 
    delete x.prop; // error: The operand of a 'delete' operator must be optional.
    delete x.a
    delete x.b
    delete x.c
    delete x.d
}
Copy the code

The resources

  • Announcing the TypeScript 4.0