DEV Community

Cover image for How I improve my skills in Typescript #5 : Satisfies operator
Code Oz
Code Oz

Posted on • Updated on

How I improve my skills in Typescript #5 : Satisfies operator

I will share with us some tips that improved my skill in Typescript ! Today we will learn satisfies operator that appear in the 4.9 TS Update !

A "Boring" error

Let's go on the common error when using Record with multiple value.

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];

const palette: Record<Colors, string | RGB> = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255]
};

const redComponent = palette.red.at(0); // Property 'at' does not exist on type 'string | RGB'.
Enter fullscreen mode Exit fullscreen mode

Hm... It's really boring, we cannot direclty use array methods since Typescript don't know if red property is a string or RGB type ! It's because we use Record<Colors, string | RGB>.

Typescript cannot narrow the correct type between string and RGB.

First solution: Narrow it

We can narrow the type !

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];

const palette: Record<Colors, string | RGB> = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255]
};

function setColor(color: RGB | string) {
    if (color instanceof Array) {
      return color.forEach(); // We can use array methods
    } else {
      return color.toUpperCase(); // It's a string ! So we can use String methods
    }
}
Enter fullscreen mode Exit fullscreen mode

It's a solution but we need to add running code for typing things.

Second solution: No type

You can fix the issue with removing type.

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255]
};

const redComponent = palette.red.at(0);
Enter fullscreen mode Exit fullscreen mode

It's also a "bad" solution, why ? You have no type, and you can add wrong key or misstypo thing !

const palette = {
    red: [255, 0, 0],
    green: null, // It should throw error
    bleu: [0, 0, 255] // It should throw error since it's not 'bleu' but 'blue'
};
Enter fullscreen mode Exit fullscreen mode

Third solution: as const

Brief introduction to as const. as const is used to block any editing on a variable, like a const assignement. This bloking allow typescript to narrow the correct type with more details.

const a: string = 'hello' // is string
const b = 'hello' as const // is 'hello'
Enter fullscreen mode Exit fullscreen mode

Let's try !

const palette = {
    "red": "yes",
    "green": false,
    "fakeKey": false, // It should throw error
    "bleu": "kinda", It should throw error
} as const;
Enter fullscreen mode Exit fullscreen mode

So it's like the old solution, there is no strict typing ! So it can leads to wrong key or misstypo !

Last solution: Create specific type from the basic type

In order to have a correct Narrowing, you can also create specific sub type for each object.

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];

type One = Record<"red", RGB> & Record<"green" | "blue", string>

const palette: One = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: "#00ff00"
}

palette.red // RGB
palette.blue // string
Enter fullscreen mode Exit fullscreen mode

It could be great since we have a correct narrowing ! But if you have a lot of key and value, I should say : Good luck my friend.

So it's the time...

Satisfies, here we go

The new satisfies operator lets us validate that the type of an expression matches some type, without changing the resulting type of that expression.

TypeScript developers are often faced with a dilemma: we want to ensure that some expression matches some type, but also want to keep the most specific type of that expression for inference purposes.

As an example, we could use satisfies to validate that all the properties of palette are compatible with string | number[]

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255]
} satisfies Record<Colors, string | RGB>

palette.red // RGB !
palette.green // string !
Enter fullscreen mode Exit fullscreen mode

It caught all possible error about typing,

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    wrongKey: "#00ff00", // Catch Error !
    bleu: [0, 0, 255], // Catch Error !
} satisfies Record<Colors, string | RGB>

palette.green = [0, 0, 255] // ! green is basically string, it cannot be RGB !
Enter fullscreen mode Exit fullscreen mode

We solve the problem !

If I should give a short definition about satifies operator, it should be the following:

satisfies operator check a type like Record<string, A | B>, and narrow the correct type between A | B for each key

Bonus, satisfies + as const

You can add all benefits of as const combinated with satifies.

In this example:

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255]
} satisfies Record<Colors, string | RGB>
Enter fullscreen mode Exit fullscreen mode

palette.red is RGB, not [255, 0, 0]. It's normal since we can edit palette.red with another RGB value like [255, 255, 255] !

If you need to have the strict value type, you can use as const.

type Colors = "red" | "green" | "blue";
// We use Readonly utils type for using as const
type RGB = Readonly<[red: number, green: number, blue: number]>

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255]
} as const satisfies Record<Colors, string | RGB>

palette.red // [255, 0, 0]
palette.red = [255, 255, 0] // throw error since we cannot edit an property that is constant
Enter fullscreen mode Exit fullscreen mode

I hope you like this reading!

☕️ You can SUPPORT MY WORKS 🙏

🏃‍♂️ You can follow me on 👇

🕊 Twitter : https://twitter.com/code__oz

👨‍💻 Github: https://github.com/Code-Oz

🇫🇷🥖 For french developper you can check my YoutubeChannel

And you can mark 🔖 this article!

Oldest comments (6)

Collapse
 
captflys profile image
CaptFlys

Very nice thanks!

Collapse
 
danielepecora profile image
Daniele Pecora

witchcraft:-D

Collapse
 
pablets profile image
Pablets

Excelent, thank you! Very usefull 👍

Collapse
 
clizhen profile image
lizhen

thank you

Collapse
 
majscript profile image
MajScript

Thanks !

Collapse
 
lionelrowe profile image
lionel-rowe

This is a very satisfying upgrade to TS