This content originally appeared on DEV Community 👩💻👨💻 and was authored by Matt Angelosanto
Written by Samuel Martins✏️
A new addition to the TypeScript programming language as of v4.9, the satisfies
operator makes it easier to write code that checks whether a value satisfies a type. Rather than checking whether it is of a specific type, satisfies
checks if a type satisfies the constraints of a generic type.
Don’t worry if you get a little bit confused in the beginning; consider the following simple example:
let x: any = "hello";
if (x satisfies string) {
console.log("x is a string");
}
As you can see, the satisfies
operator checks if the value of x
is a string, and if it is, it logs a message to the console. Going by the example alone, you can already notice that the satisfies
operator is a type guard. Therefore, it allows the TypeScript compiler to narrow the type of a value based on the result of the check, which can be useful for optimizing type checking in your code.
By the end of this article, you’ll understand what problem satisfies
solves, how to use it in your code, and the benefits of using it in your codebase. Let’s get started!
Jump ahead
- What is the
satisfies
keyword? - What problem does
satisfies
solve? - How does
satisfies
solve the type checking problem? - A more complex example of
satisfies
- Different ways to use
satisfies
in your code - Benefits of using
satisfies
in your code
What is the satisfies
keyword?
The satisfies
keyword in TypeScript is a type guard that allows you to check if a value matches a specific type. This is useful when working with complex data structures or when dealing with values that can be of multiple types.
The satisfies
keyword is used in type constraints to indicate that a given type must be compatible with a certain interface or must extend a certain class. For example, if you have a function that takes a parameter of type T
, you can use a type constraint to specify that T
must be a class that extends the BaseWidget
class, like this:
function createWidget<T extends BaseWidget>(widget: T): T {
// The code for this function goes here
}
The type constraint T extends BaseWidget
specifies that the type parameter T
must be a class that extends the BaseWidget
class. Therefore, any value passed to the createWidget
function must be an object of a class that extends BaseWidget
. Otherwise, the TypeScript compiler will throw an error. This is just a quick high level example; we’ll cover more examples later on.
What problem does satisfies
solve?
TypeScript still works without satisfies
, so you may be wondering exactly what problem it solves. Is it just solutionism at its worst? Well, one common problem that the satisfies
keyword solves is type checking within conditional statements.
If you don't use the satisfies
operator in your code, you won't be able to take advantage of its type-checking capabilities. Therefore, the TypeScript compiler won't be able to narrow the type of a value based on the result of a satisfies
check, and you may end up with type errors or other problems in your code.
For example, let's say you have a variable x
with the type any
, which means that it can hold any type of value. Without using the satisfies
operator, you might try to access a property on x
that is only available on strings:
let x: any = "hello";
let y = x.toUpperCase();
In this case, the TypeScript compiler will generate an error because it doesn't know that the value of x
is actually a string. However, if you use the satisfies
operator, you can tell the compiler that the value of x
is a string, and it will allow you to access the toUpperCase
property:
let x: any = "hello";
if (x satisfies string) {
let y = x.toUpperCase(); // OK: property 'toUpperCase' exists on type 'string'
}
How does satisfies
solve the type checking problem?
The satisfies
keyword approaches this type checking problem by providing a concise and intuitive syntax for checking the type of a value. To use the satisfies
keyword, you simply specify the type that you want to check for within the satisfies
clause.
When the TypeScript compiler encounters a satisfies
check in your code, it will evaluate the check and narrow the type of the value being checked based on the result. For example, if you have a variable x
with the type any
, and you check if it satisfies the type string
, the compiler will narrow the type of x
to string
if the check evaluates to true
.
Below is an example of how this works in practice. I’ll use the same example from above:
let x: any = "hello";
if (x satisfies string) {
// At this point, the compiler knows that the type of x is 'string'
let y = x.toUpperCase(); // OK: property 'toUpperCase' exists on type 'string'
}
In this example, the satisfies
check evaluates to true
, so the compiler narrows the type of x
to string
. This allows you to access the toUpperCase
property on x
without getting a type error.
A more complex example of satisfies
So far, I’ve played it safe with the examples just to get your feet wet. You probably won’t use hello
string types in your code, so consider the following, more realistic example:
type User = {
id: number;
name: string;
email: string;
isAdmin: boolean;
};
function getUserById(id: number): User | undefined {
// Fetch user from the database...
// For this example, we'll just return a hard-coded user object
return {
id: 1,
name: "John Doe",
email: "johndoe@example.com",
isAdmin: false
};
}
function getUserName(id: number): string | undefined {
let user = getUserById(id);
if (user satisfies User) {
// At this point, the compiler knows that the type of user is 'User'
return user.name;
} else {
return undefined;
}
}
let userName = getUserName(1);
console.log(userName); // Output: 'John Doe'
In the example above, we have a getUserById
function that fetches a user from the database by their ID. The function returns a User
object if the user is found, or undefined
if the user is not found.
We also have a getUserName
function that takes a user ID as an argument, returns the user's name if the user is found, and returns undefined
if the user is not found. Inside the getUserName
function, we use the satisfies
operator to check if the user
variable is of type User
, and if it is, we return the user's name.
Different ways to use satisfies
in your code
Now that you know how satisfies
works and how it approaches the type checking problem, we'll explore the different ways you can use it.
Narrowing the type of a value based on satisfies
let x: any = "hello";
if (x satisfies string) {
// At this point, the compiler knows that the type of x is 'string'
let y = x.toUpperCase(); // OK: property 'toUpperCase' exists on type 'string'
}
In the code above, you can determine through inference that the compiler already knows that the type of
is a string.
The if
statement condition only checks if x
fits the criteria by testing the toUpperCase
method. If this variable was a number instead, the if
statement would have returned false
, and therefore, line 4 would not be reached. The same applies to the inline assertion and the type guard functions, below.
Using the satisfies
operator in an inline type assertion
let x: any = "hello";
let y = (x as string).toUpperCase(); // OK: property 'toUpperCase' exists on type 'string'
Using the satisfies
operator in a type guard function
function isString(x: any): x is string {
return x satisfies string;
}
let x: any = "hello";
if (isString(x)) {
// At this point, the compiler knows that the type of x is 'string'
let y = x.toUpperCase(); // OK: property 'toUpperCase' exists on type 'string'
}
Using the satisfies
operator in a type predicate
type IsStringOrNumber<T> = T extends string | number ? T : never;
function isStringOrNumber<T>(x: T): x is IsStringOrNumber<T> {
return x satisfies string || x satisfies number;
}
let x: any = "hello";
if (isStringOrNumber(x)) {
// At this point, the compiler knows that the type of x is either 'string' or 'number'
if (x satisfies string) {
let y = x.toUpperCase(); // OK: property 'toUpperCase' exists on type 'string'
} else {
let y = x.toFixed(2); // OK: method 'toFixed' exists on type 'number'
}
}
A type predicate is basically a function that takes in a value and returns a boolean indicating whether or not the value is of a certain type or satisfies a given condition.
Typically, these are used in conjunction with type guards. Consider the example above. The function defined on line 3 basically returns if
whether x
is of type string
or number
. When it is called on line 8, the compiler is already aware of its type depending on the value of x
on line 7. That being said, the nested if
statement is basically a type guard and executes based on what the condition returns.
In this case, line 11 runs; the definition of x
on line 7 and the check on line 8 basically shows that x
is a string.
Benefits of using satisfies
in your code
There are several benefits of using the satisfies
operator in your TypeScript codebase, some of which include improved type checking, better code organization, and enhanced code efficiency.
The satisfies
operator allows the TypeScript compiler to narrow the type of a value based on the result of a check, which can help prevent type errors and improve the overall type-checking capabilities of your code.
By using the satisfies
operator, you can organize your code into logical blocks based on the type of a value. This can make your code easier to read and more understandable, also helping to prevent repeating type checks in different parts of your code.
Finally, because the satisfies
operator allows the compiler to narrow the type of a value, it can help make your code more efficient by reducing the amount of type checking that must be done at runtime, resulting in faster and more efficient code execution.
Conclusion
The satisfies
operator offers a more concise and flexible way to check whether a value satisfies a given type. It is a valuable addition to TypeScript that makes it easier to write code that performs type checking. It allows for more precise and flexible type checking, and it can make your code simpler and more readable even when working with complex types hierarchies.
LogRocket: Full visibility into your web and mobile apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
This content originally appeared on DEV Community 👩💻👨💻 and was authored by Matt Angelosanto
Matt Angelosanto | Sciencx (2023-02-08T16:28:48+00:00) Exploring the satisfies operator in TypeScript. Retrieved from https://www.scien.cx/2023/02/08/exploring-the-satisfies-operator-in-typescript/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.