4

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

 1 year ago
source link: https://dev.to/codeoz/how-i-improve-my-skills-in-typescript-5-satisfies-operator-2312
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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'.

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
    }
}

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);

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'
};

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'

Let's try !

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

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

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 !

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 !

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>

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

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!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK