<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 "> Bitovi Blog - UX and UI design, JavaScript and Front-end development

TypeScript 4.9 Features and Improvements

Josh Glantz-Hucks

TypeScript 4.9 has exciting new features and improvements for frontend/React developers. Read about our favorites and learn how to download TypeScript 4.9

posted in React, TypeScript on November 15, 2022 by Josh Glantz-Hucks


TypeScript 4.9 Features and Improvements

Josh Glantz-Hucks by Josh Glantz-Hucks

It’s always exciting for developers when languages release new versions—whether they fix issues, introduce new features, or both! The release of TypeScript 4.9 on November 15th, 2022 introduces a new operator, performance improvements, plus a variety of new features and capabilities. Allow us to introduce 3 of our favorites:

  • Disallowing comparisons against NaN
  • New satisfies operator
  • Improvements to the in operator

Disallows Comparison Against "NaN"

Checking for equality against the value NaN presents a gotcha in many languages including JavaScript. Nothing ever equals NaN, including itself, but since NaN is somewhat rare, it's easy to forget the implications of that. This often comes up with number parsing resulting in NaN, where it's easy to fall into the mistake of checking with someValue === NaN.

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

if (someNumber !== NaN) {
	//will always return true
}

TypeScript now helps prevent this by erroring on direct comparisons against NaN and will instead suggest a correct variation.

if someNumber !== NaN) {
//            ~~~
// error! Condition will always return true since NaN !== NaN. Use Number.isNan() 
}

Satisfies Operator

The new satisfies operator allows us to check that an expression matches some type, without changing the type itself. We’ll cover how this provides a new validation check, but it can also be used for undoing the specificity of an initializer (“safe upcasting”) and more, as described here.

Validation Check without Assigning Type

Frequently we want to check that a variable has the right type, but we don’t always want the to change the type in the process. Using a type annotation in the example below actually changes the type of wishlist by always letting each key be either a string or an array of numbers. This quickly creates a problem where any methods used on those values must be able to deal with both types.

type Attractions =  "historical" | "stargazing";
type GPSCoordinates = [latitude: number, longitude: number];

const wishlist: Record<Attractions, string | GPSCoordinates> = { //type assigned here
	historical: [ 35.41, 139.42],
	stargazing: "Atacama Desert, Chile"
}

For example, after annotating type, we can no longer use wishlist.stargazing.toLowerCase(), because the value could now be GPSCoordinates type. The new satisfies operator allows us to bypass the problem by only validating that our expression matches a type. It is a validation check instead of a type assignment. Let's check that wishlist has the properties desired without effecting the types of those properties:

type Attractions =  "historical" | "stargazing";
type GPSCoordinates = [latitude: number, longitude: number];

const wishlist = {
	historical: [ 35.41, 139.42], //type assigned here
	stargazing: "Atacama Desert, Chile" //type assigned here
} satisfies Record<Attractions, string | GPSCoordinates>; // validation checked here

In our new code, wishlist.stargazing.toLowerCase() can now be used thanks to TypeScript's inference. Our validation check insures that wishlist has the proper types inside of it, without forcing any of those properties to always be string | GPSCoordinates.

Note: satisfies is also checking that wishlist's keys match the strings in Attractions and will error if there are any other keys or missing keys (property name constraining and fulfillment).

Improving the "in" operator

Checking for Valid Property Keys

The existing in operator now has improved checking. Previously, in the expression key in obj, key was allowed to be an unconstrained type parameter type. Now it requires the type of key to be assignable to string | number | symbol. Likewise, the type of obj was previously allowed an unconstrained generic type, but is now required to be assignable to object. Both of these now ensure we are not accidentally checking primitives and avoids throwing exceptions at runtime.

Unlisted Property Narrowing with the "in" Operator

When trying to narrow the potential types, TypeScript previously did nothing in key in obj when key named a property that doesn't exist in type of obj. This is relevant when we try to safely type a property with unknown:

interface Context {
    packageJSON: unknown;
}

function tryGetPackageName(context: Context) {
    const packageJSON = context.packageJSON;
    // Checks and narrows type to `object`
    if (packageJSON && typeof packageJSON === "object") {
        // Check for string name property. Does not narrow type.
        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;
}

The above code, borrowed from the release announcement shows how TypeScript would narrow packageJSON from unknown to obj, but wouldn't be able to narrow it further when checking for "name" in packageJSON. Since the "name" property is unlisted (ie: not actually defined), the type is not narrowed down from 'object' to objects containing "name".

In 4.9, Typescript will now narrow any types according to the unlisted property by intersecting their types with Record<"property-key-being-checked", unknown>. So in our example code, instead of packageJSON only being narrowed to object it will further be narrowed to object & Record<"name", unknown>:

interface Context {
    packageJSON: unknown;
}

function tryGetPackageName(context: Context): string | undefined {
    const packageJSON = context.packageJSON;
    // Checks & narrows type to `object`
    if (packageJSON && typeof packageJSON === "object") {
        // Checks and narrows type to `object & Record<"name", unknown>`
        if ("name" in packageJSON && typeof packageJSON.name === "string") {
            // now works!
            return packageJSON.name;
        }
    }

    return undefined;
}

As shown, packageJSON.name can now be accessed directly!

Updating to TypeScript 4.9

If you're ready to upgrade to the latest TypeScript version, we’ll show you how. Using npm:

npm i typescript@latest

Using yarn:

yarn add typescript@latest

Just as straight-forward is upgrading (or downgrading) to a specific version like 4.9. Instead of @latest, use @<version-number> like so:

npm i typescript@4.9

Using yarn:

yarn add typescript@4.9

Final Thoughts

TypeScript 4.9 contains exciting features and improvements. Don’t just take our word for it, try it for yourself!

React Consulting

Have more questions? We’d love to take a look at your project. Book a free consultation call to get started.

 

Create better web applications. We’ll help. Let’s work together.