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.