いい加減satisfiesを理解したい【TypeScript】

# はじめに

今回はTypeScriptのsatisfiesについて理解を深めたい記事になります。(乱文)
毎回フィーリングで使っていて,ちゃんと理解していないなと思い学習兼備忘録として書いていきます。

## satisfiesとは

日本語に訳すと「満たす」という意味になります。

その名の通り、型が指定した条件を満たすかどうかを判定するための関数です。

使い方としては、`式 satisfies 型`という形で使います。

## typescriptの公式から引用

> satisfies演算子を使用すると、式の結果の型を変更せずに、式の型が何らかの型と一致することを検証できます

これはどういうことなのか、具体的な例を見ていきましょう。

```typescript
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],
};

// 'palette.green' は型 'string | RGB' である可能性があり、
// プロパティ 'toUpperCase' は型 'string | RGB' に存在しません。
const greenNormalized = palette.green.toUpperCase();
```

このとき、greenNormalizedは以下のような型推論がされてしまい、`palette.green`はstring型のはずなのにnumberの可能性があると推論されてしまうためエラーが発生します。

![スクリーンショット 2024-12-23 23.52.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2778030/778f11c5-612f-f8c8-a322-713c5ba40b56.png)

ここで`satisfies`を使って型を指定することでエラーを回避することができます。

```typescript
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>; // <--- satisfiesを使って型を推論

const greenNormalized = palette.green.toUpperCase(); // エラーが発生しない!
```

`palette`をホバーすると以下のように型が推論されていることがわかります。

![スクリーンショット 2024-12-23 23.56.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2778030/176361e6-687e-a907-2462-73574abf50db.png)

これが、冒頭の「satisfies演算子を使用すると、式の結果の型を変更せずに、式の型が何らかの型と一致することを検証できます」という意味なんですね。

## 活用例

satisfiesを使うことで何が嬉しいのかはわかったけど、実際にどういった場面で使うのかがわからなかったのでそこも調べてみました。

### 1. 型を推論しつつ、保証する

こちらは「typescriptの公式から引用」セクションで紹介した例と同じですが、オブジェクトの型を推論しつつ、型を保証することができます。

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

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

こちらはほとんど同じように見えますが、型推論の結果が異なります。
変数をホバーすると以下のように型が表示されます。

```typescript
const palette: {
  red: [number, number, number];
  green: string;
  blue: [number, number, number];
};

const palette2: Record<Colors, string | RGB>;
```

これはつまり、`palette2.green.toUpperCase()`はエラーが発生してしまいますが、`palette.green.toUpperCase()`はエラーが発生しないということです。(palette2のバリューはRGB型である可能性があるため)

### 2. `as const`と併用することで型チェックしつつwideningを防ぐ

wideningとは、型の定義が不必要に広がることを指します。
`as const`は、今回の例ではオブジェクトをreadonlyにするために使われています。

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

// const palette3: {
//   readonly red: [255, 0, 0];
//   readonly green: "#00ff00";
//   readonly blue: [0, 0, 0];
// }
```

コメントアウト部がホバーした結果ですが、`as const`と`satisfies`を併用することで、型を推論しつつwideningを防ぐことができます。
定数を定義するときには積極的に使っていきたいですね。

また、配列に対しても同様に使うことができます。

```typescript
const arr = [1, 2, 3];
// const arr: number[]

const arr1 = [1, 2, 3] as const satisfies readonly number[];
// const arr1: readonly [1, 2, 3]

const strArray = ['red', 'green', 'blue'] as const satisfies readonly string[];
// const strArray: readonly ["red", "green", "blue"]
```
参考:

https://zenn.dev/tonkotsuboy_com/articles/typescript-as-const-satisfies

### 3. ユニオン型の網羅性チェック

switch文での網羅性チェックを行う際にも使うことができます。

```typescript
type Colors = "red" | "green" | "blue";
const getHex = (color: Colors) => {
    switch (color) {
        case 'red':
            return '#FF0000';
        case 'green':
            return '#00FF00';
        case 'blue':
            return '#0000FF';
        default:
            return color satisfies never;
    }
}
```
このとき、たとえばblueを削除してしまった場合、`default`の部分でエラーが発生します。

![スクリーンショット 2024-12-24 0.40.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2778030/7e42c622-aa65-f528-0819-b55465ba7988.png)

ユニオン型が更新され、値が増えたときにもエラーが発生するので変更に強くなります。

## まとめ

`satifies`は型を推論しつつ、型を保証することができる便利な演算子です。

使用することで、型推論が便利になったりパターンマッチのようなことができたりします。

## おわりに
最後までお読みいただきありがとうございました！

今回記事を書いて、なんとなくstiafiesの使い所を理解できた気がします。

## 参考

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html

https://zenn.dev/tonkotsuboy_com/articles/typescript-as-const-satisfies

https://zenn.dev/gemcook/articles/17f7a25dc88cb9