意外と知らない? satisfies演算子の良さ
はじめに
こんにちは。
鈴木商店でフロントエンドエンジニアをしているやすのりです。
今やフロントエンド開発では切っても切り離せない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; // ←エラー
上記コードだと本来grape
はkcal
のプロパティを内包したオブジェクトを持っているので、
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演算子
を使用して型推論の結果を保持した状態だと添付画像の通り、
apple
とgrape
にCalorieInfo
が紐付いているのがわかるようになりましたね🧐
結局どんな時に役立つの?
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文を記述するときに
apple
やbanana
等のサジェストはしてくれるんですけど過不足チェックまではしてくれません。
過不足チェックがあると、このユニオン型が変更された時に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