TypeScript's satisfies operator has been out for a while, but it still seems to be a source of confusion that could use some clearing up.
Think of satisfies as another way to assign types to values.
Before we look closer, let's do a review of how to assign types.
First, there's the humble "colon annotation" (we'll go with this slightly medical-sounding name since this concept isn't really given a name in the TS docs).
The : says "this variable is always this type":
When you use a colon annotation, you're declaring that the variable is that type.
That means that the thing you assign to the variable must be that type:
It’s possible to give a variable a type that’s wider than the one you initially assign.
This is useful when you want to have a default which might later be reassigned.
But colon annotations come with an edge-case downside.
When you use a colon, the type BEATS the value.
In other words, if you declare a wider type than you want, you're stuck with the wider type.
For example, in this snippet you don't get autocomplete on the routes object:
This is the problem satisfies was designed to solve.
When you use satisfies
, the value BEATS the type.
This means it infers the narrowest possible type, not the wider type you specify:
satisfies
also protects you from specifying the wrong thing inside your config object.
So, colon annotations and satisfies
are equally safe.
Another way you can assign types to variables is the ‘as
’ annotation.
Unlike satisfies
and colon annotations, using ‘as
’ annotations lets you lie to TypeScript.
In this example, you wouldn’t see an error in your IDE but it will break at runtime:
There are some limits to the lies– you can add properties to objects, but you can’t convert between basic types.
For instance, you can’t force TypeScript to convert a string into a number…
…unless you use the monstrous ‘as-as’:
Here’s a legit usage of as
, where it is used to convert an object to a known type that hasn’t been constructed yet:
A word of caution: if you’re using as
as your default way to annotate variables, that’s almost certainly wrong!
The code below might look safe, but as soon as you add another property to the User
type, the defaultUser
will be out of date, and it will not show you an error!
There’s one last way to give a type to a variable:
Don’t.
That’s not a typo!
TypeScript does an amazing job at inferring types for your variables.
In fact most of the time, you won’t need to type your variables at all:
To recap, we’ve got FOUR ways of assigning a type to a variable:
- colon annotations
satisfies
as
annotations- not annotating and letting TS infer it
With different ways to do similar things, it can get a bit confusing about when to use what.
Simple use cases are where satisfies
works best:
But most of the time, when you want to assign a type to a variable you probably want the type to be wider.
If this example used satisfies
, you wouldn’t be able to assign numericId
to id
:
The rule of thumb is that you should only use satisfies in two specific situations:
- You want the EXACT type of the variable, not the WIDER type.
- The type is complex enough that you want to make sure you didn’t mess it up
Total TypeScript Core Volume includes dozens of challenges in this vein that will help you solidify your mental model for type assignments and transformations!