How I improve my skills in Typescript #5 : Satisfies operator
Tags: typescript, javascript, webdev, programming
Published: 2023-03-09 11:31:54

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.

```ts
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 !

```ts
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.

```ts
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 !

```ts
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.

```ts
const a: string = 'hello' // is string
const b = 'hello' as const // is 'hello'
```

Let's try !

```ts
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.

```ts
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[]

```ts
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,

```ts
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:

```ts
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`.

```ts
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](https://ko-fi.com/codeoz) 🙏

🏃‍♂️ You can follow me on 👇

🕊 Twitter : [https://twitter.com/code__oz](https://twitter.com/code__oz)

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

🇫🇷🥖 For french developper you can check my [YoutubeChannel](https://www.youtube.com/channel/UCC675U1ZUPFASsK9-FjawtA)

And you can mark 🔖 this article!