🤔

意外と知らない? satisfies演算子の良さ

2025/02/27に公開

はじめに

こんにちは。
鈴木商店でフロントエンドエンジニアをしているやすのりです。

今やフロントエンド開発では切っても切り離せないTypeScriptですが、
TypeScriptもどんどん進化を重ねて今や5.7まできましたね。

型アサーションやら型エイリアスなどなど
みんないろんな機能を使って効率よく開発をしていると思いますが、
今日は周りで意外と使われていないsatisfies演算子について書きたいと思います。

ただ『そもそもsatisfies演算子ってなんですの?』っていう人もいると思うので軽く紹介します。

satisfies演算子ってなに?

2022年11月25日にリリースされたTypeScript4.9で追加された新規オペレータで、
式 satisfies 型と指定して式の型チェックができます。
例を挙げると下記みたいなコードの書き方ですね。

interface User {
 id: number;
 name: string;
}

const Yasunori = { id: 1, name: 'やすのり' } satisfies User;

型アノテーションとの違いは?

『式の型チェックができるのは型アノテーションも一緒じゃない?』って思ったそこの貴方!

実はsatisfies演算子には型アノテーションには無い型推論の結果を保持する機能があります。
これがかなり重要なんです、具体的に見てみましょうか。

type Fruits = 'apple' | 'banana' | 'grape';
type CalorieInfo = { kcal: number };

const fruitsCalories: Record<Fruits, number | CalorieInfo> = {
  apple: { kcal: 52 },
  banana: 89,
  grape: { kcal: 69 },
};

// プロパティ 'kcal' は型 'number | CalorieInfo' に存在しません。
const grapeKcal = fruitsCalories.grape.kcal; // ←エラー

上記コードだと本来grapekcalのプロパティを内包したオブジェクトを持っているので、
kcalにアクセスできるはずですよね?
ただ型自体はgrapeの中身がnumber | CalorieInfoになっているため、
kcalがあるか判断できないんです。

ではこのコードにsatisfies演算子を適用したらどうなるのか見てみましょう。

type Fruits = 'apple' | 'banana' | 'grape';
type CalorieInfo = { kcal: number };

const fruitsCalories = {
  apple: { kcal: 52 },
  banana: 89,
  grape: { kcal: 69 },
} satisfies Record<Fruits, number | CalorieInfo>; // ←satisfiesでの型推論 & チェック

const grapeKcal = fruitsCalories.grape.kcal; // ←エラーにならない

satisfies演算子を使用して型推論の結果を保持した状態だと添付画像の通り、
applegrapeCalorieInfoが紐付いているのがわかるようになりましたね🧐

結局どんな時に役立つの?

satisfies演算子について紹介してきましたが
『有用そうなのはわかったけど、どこで使えんの?』ってなってませんか?
正直僕も当初そう思っていたんですけど、調べてみると結構いろいろ活用できます。

・型推論結果を保持しつつ、型チェックをする

これは#型アノテーションとの違いは?で説明した通りなので割愛します。

・定数定義の型チェックとwidening予防

こちらについては僕自身も参考にしたメチャクチャわかりやすい記事があるので、
そちらをご確認ください

・switch文での選択肢網羅チェック

ユニオン型の変数を使用したswitch文を記述する時に条件の過不足がチェックできます。

type Fruits = 'apple' | 'banana' | 'grape';

const getCalories = (fruit: Fruits) => {
  switch (fruit) {
    case 'apple':
      return 52;
    case 'banana':
      return 89;
    default:
      // grape の条件が case に無いため型エラー
      return fruit satisfies never;
  }
};

const getCalories2 = (fruit: Fruits) => {
  switch (fruit) {
    case 'apple':
      return 52;
    case 'banana':
      return 89;
    case 'grape':
      return 69;
    // orange が Fruits に内包されていない型のためエラー
    case 'orange':
      return 78;
    default:
      return fruit satisfies never;
  }
};

satisfies演算子を使用しなくてもcase文を記述するときに
applebanana等のサジェストはしてくれるんですけど過不足チェックまではしてくれません。

過不足チェックがあると、このユニオン型が変更された時にswitch文の変更漏れを心配する必要がなくなります。

・テスト内のモックデータのundefined排除

テストコードでモックデータを作成する時に、
オプショナルプロパティに値を入れてテストしたい場合ってもちろんあると思います。

ただそうした場合に型アノテーションを使うと余計な判定式が必要になったりしてしまいます。

type User = {
  id: number;
  name: string;
  age?: number; // 年齢は省略可能
};

function getAge(user: Required<User>) {
  return user.age;
}

// テストコード
const mockUser: User = { id: 1, name: 'Yasunori', age: 25 }; // ←型アノテーションで型チェック

// 型 'number | undefined' を型 'number' に割り当てることはできません。
const result = getAge(mockUser); // ←型エラーが発生

expect(result).toBe(mockUser.age);

上記のエラーを解決するとしたらgetAge関数を実行する前に
ageプロパティの存在チェックしたり等々の余計なコードが必要になってきます。

ですがこのコードの型アノテーションsatisfies演算子に変えるだけで型エラーを防げます。

const mockUser = { id: 1, name: 'Yasunori', age: 25 } satisfies User; // ←satisfies演算子で型推論結果を保持

const result = getAge(mockUser); // ←型エラーにならない

まとめ

リリースされてすでに2年以上経過したsatisfies演算子
TypeScriptでの開発を始めて1年弱ですが、実際僕も使い始めたのはここ数週間なので、
これを機に皆さんも良いsatisfiesライフ、送ってみませんか?

この記事は記事投稿しているフロントエンドの諸先輩方の記事を参考にさせて頂きつつ作成しました。
下記に参考にさせていただいた記事を掲載しておきますので、
是非ともそちらもご確認ください

参考元

Discussion