Microsoft announces the availability of TypeScript 4.9, which adds the new satisfies operator, improves the in operator and already supports ECMAScript automatic accessors


The TypeScript team has just announced the availability of the final version of TypeScript 4.9. For this latest version, many significant improvements are on the agenda. These include support for an upcoming feature in ECMAScript called auto-accessors , enhanced in operator capabilities, the addition of a new operator called satifies , and the introduction of the Remove Unused Imports command, to name a few. -I.

operator satisfied

The new operator satisfied allows you to validate that the type of an expression corresponds to a certain type without changing the resulting type of this expression. As an example, we could use satisfied to validate that all the properties of palette are compatible with string | number[]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

type Colors = "red" | "green" | "blue";
 
type RGB = [red: number, green: number, blue: number];
 
const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255]
//  ~~~~ The typo is now caught!
} satisfies Record<Colors, string | RGB>;
 
// Both of these methods are still accessible!
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();

operator satisfied can be used to detect many possible errors. For example, to ensure that an object has all the keys of a certain type, but no more:

1
2
3
4
5
6
7
8
9
10
11
12
13

type Colors = "red" | "green" | "blue";
 
// Ensure that we have exactly the keys from 'Colors'.
const favoriteColors = {
    "red": "yes",
    "green": false,
    "blue": "kinda",
    "platypus": false
//  ~~~~~~~~~~ error - "platypus" was never listed in 'Colors'.
} satisfies Record<Colors, unknown>;
 
// All the information about the 'red', 'green', and 'blue' properties are retained.
const g: boolean = favoriteColors.green;

We may not be interested in whether the property names somehow match, but the types of each property are important enough to do some type checking. In this case, we can also ensure that all property values ​​of an object conform to a certain type.

1
2
3
4
5
6
7
8
9
10
11
12

type RGB = [red: number, green: number, blue: number];
 
const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0]
    //    ~~~~~~ error!
} satisfies Record<string, string | RGB>;
 
// Information about each property is still maintained.
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();

Limitation of unlisted properties with operator in

As a developer, you often have to deal with values ​​that are not fully known at runtime. In fact, we often don't know if properties exist, if we get a response from a server, or if we read a configuration file. operator in of JavaScript can check if a property exists on an object.

Previously, TypeScript allowed us to restrict all types that didn't explicitly list a property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

interface RGB {
    red: number;
    green: number;
    blue: number;
}
 
interface HSV {
    hue: number;
    saturation: number;
    value: number;
}
 
function setColor(color: RGB | HSV) {
    if ("hue" in color) {
        // 'color' now has the type HSV
    }
    // ...
}

In the example above, the RGB type didn't list the hoot and was reduced, leaving us with the HSV type. But what about examples where no type lists a given property? In these cases, the language does not help much. Consider the following example in JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12

function tryGetPackageName(context) {
    const packageJSON = context.packageJSON;
    // Check to see if we have an object.
    if (packageJSON && typeof packageJSON === "object") {
        // Check to see if it has a string name property.
        if ("name" in packageJSON && typeof packageJSON.name === "string") {
            return packageJSON.name;
        }
    }
 
    return undefined;
}

Rewriting this in canonical TypeScript would simply be a matter of defining and using a type to background; however, choosing an sr type like unknownfor the property packageJSONwould cause problems in older versions of TypeScript.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

interface Context {
    packageJSON: unknown;
}
 
function tryGetPackageName(context: Context) {
    const packageJSON = context.packageJSON;
    // Check to see if we have an object.
    if (packageJSON && typeof packageJSON === "object") {
        // Check to see if it has a string name property.
        if ("name" in packageJSON && typeof packageJSON.name === "string") {
        //                                              ~~~~
        // error! Property 'name' does not exist on type 'object.
            return packageJSON.name;
        //                     ~~~~
        // error! Property 'name' does not exist on type 'object.
        }
    }
 
    return undefined;
}

Indeed, while the type of packageJSON was reduced from unknown object, the in operator was strictly limited to types that actually defined the checked property. Consequently, the type of packageJSONstayed object.

TypeScript 4.9 makes operator in a bit more powerful when reducing types that don't list the property at all. Instead of leaving them as they are, the language will intersect their types with Record<"property-key-being-checked", unknown>. So, in the example above, packageJSONwill have its reduced type of unknown object & Record<"name", unknown>. This allows direct access packageJSON.name and reduce that independently.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

interface Context {
    packageJSON: unknown;
}
 
function tryGetPackageName(context: Context): string | undefined {
    const packageJSON = context.packageJSON;
    // Check to see if we have an object.
    if (packageJSON && typeof packageJSON === "object") {
        // Check to see if it has a string name property.
        if ("name" in packageJSON && typeof packageJSON.name === "string") {
            // Just works!
            return packageJSON.name;
        }
    }
 
    return undefined;
}

TypeScript 4.9 also tightens some controls over how inis used, ensuring that the left side is attributable to the kind string | number | symbol and that the right side is attributable object. This ensures that valid property keys are used and that primitives are not accidentally checked.

Automatic Accessors in Classes

TypeScript 4.9 supports an upcoming feature in ECMAScript called auto-accessors. An auto-accessor is a field declaration that will be transformed by the runtime into a pair of get and set accessors that access a private backing field. Autoaccessors are declared like properties on classes, except they are declared with the keyword accessory.

1
2
3
4
5
6
7

class Person {
    accessor name: string;
 
    constructor(name: string) {
        this.name = name;
    }
}

Below are these automatic accessors transformed into get and set with an inaccessible private property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14

class Person {
    #__name: string;
 
    get name() {
        return this.#__name;
    }
    set name(value: string) {
        this.#__name = name;
    }
 
    constructor(name: string) {
        this.name = name;
    }
}

Equality checks on Nope

A major pitfall for JavaScript developers is to check the NaN value using the built-in equality operators. For some, NaN is a special numeric value that means "Not a Number". Nothing is ever equal NaN even NaN!

1
2
3
4
5

console.log(NaN == 0)  // false
console.log(NaN === 0) // false
 
console.log(NaN == NaN)  // false
console.log(NaN === NaN) // false

But at least symmetrically everything is always non-gal NaN.

1
2
3
4
5

console.log(NaN != 0)  // true
console.log(NaN !== 0) // true
 
console.log(NaN != NaN)  // true
console.log(NaN !== NaN) // true

The problem is that JavaScript's main numeric type is a floating-point number, and parsing numbers in JavaScript can often give Nope. in turn, check against Nope ends up being quite common, and the correct way to do this is to use Number.isNaN; but many people accidentally end up checking with someValue === Nope.

TypeScript now errs on direct comparisons with NaN, and will instead suggest using a variant of Number.isNaN.

1
2
3
4
5
6

function validate(someValue: number) {
    return someValue !== NaN;
    //     ~~~~~~~~~~~~~~~~~
    // error: This condition will always return 'true'.
    //        Did you mean '!Number.isNaN(someValue)'?
}

This change should help catch beginners' errors, similar to how TypeScript currently throws errors when comparing against object and array literals.

The orders Remove Unused Imports and Sort Imports for publishers

Previously, TypeScript only supported two editor commands to handle imports. The first was called Organize Imports (or Organize Imports) which removed unused imports, then sorted the remaining ones. In TypeScript 4.3, a new command called Sort Imports (or Sort Imports) which only sorts the imports in the file, but does not remove them has been added. The caveat with the Sort Imports command was that in Visual Studio Code, this feature was only available as an on-record command, not a manually triggerable command.

TypeScript 4.9 adds the other half and now provides Remove Unsed Imports (for Remove Unused Imports). TypeScript will now remove unused names and import statements, but otherwise leave the relative order.

Substitute replaced by coercion on objects SubstitutionTypes

Within the framework of an optimization on the substitution types, the objects SubstitutionTypeno longer contain the property substituterepresenting the effective override (usually an intersection of the base type and the implicit constraint) instead they simply contain the property coercion.

Beyond the new things introduced in this latest version of TypeScript, several other enhancements are also available like performance enhancements, bug fixes and much more.

Source: Microsoft

And you?

Have you tested the new version of TypeScript? does it meet your expectations?

What features did you like most or least about this latest version of TypeScript?

See as well

TypeScript 4.9 Beta is available and brings the restriction of unlisted properties with in operator, as well as the legality check on NaN
TypeScript 4.7 Beta comes with ECMAScript module support in Node.js and offers module detection control
TypeScript 4.3 is released and brings the override keyword so a dev can clearly indicate whether they intended to add a new method or replace an existing method
Microsoft releases TypeScript 4.2 with support for Abstract Constructor Types and tighter controls for in operator

Leave a Comment