🛠️
satisfies を使って嬉しかったところ
はじめに
既存のプロダクトで、TypeScript のバージョンをアップデートし satisfies を使った実装を試してみました。
その中で、satisfies を使って特に嬉しかったところを書かせていただきます。
satisfies とは
- TypeScript 4.9 で追加された演算子
- 式と型がマッチしているかチェックする
- const assertion と併用できる
- annotation との違いとして推論結果を保持できる
詳しくは公式のドキュメントや、こちらの記事にわかりやすく紹介されています。
嬉しかったところ
個人的に既存のプロダクトにおいて以下のパターンで satisfies を使った実装にするととても嬉しかったです。
ある関心ごとのステータスを定義したオブジェクトと、そのバリューのユニオン型が定義されている実装がありました。(ここではポケモンの状態異常を例にさせていただいています)
type ValueOf<T, K extends keyof T = keyof T> = T[K]
const ailmentStatus = {
Burn: 0,
Freeze: 1,
Paralysis: 2,
Poison: 3,
Sleep: 4,
Fainting: 99,
} as const
type AilmentNumber = ValueOf<typeof ailmentStatus>
// AilmentNumber は 0 | 1 | 2 | 3 | 4 | 99
この ailmentStatus オブジェクトには意図しない値が入ってしまう可能性があります。
const ailmentStatus = {
Burn: 0,
Freeze: 1,
Paralysis: 2,
Poison: 3,
Sleep: 4,
Fainting: 99,
Terastal: 10000, // 意図していない Terastal が入ってしまう
} as const
Terastal のような意図しない値が入ってしまわないように annotation を使って実装すると以下のようになります。
type Ailment = 'Burn' | 'Freeze' | 'Paralysis' | 'Poison' | 'Sleep' | 'Fainting'
type AilmentStatus = {
[key in Ailment]: number
}
const ailmentStatus: AilmentStatus = {
Burn: 0,
Freeze: 1,
Paralysis: 2,
Poison: 3,
Sleep: 4,
Fainting: 99,
Terastal: 10000, // 'Terastal' は型 'AilmentStatus' に存在しません。
} as const
type AilmentNumber = ValueOf<typeof ailmentStatus>
// AilmentNumber は number
こうすることによって型エラーが出るようになり、意図しない値が入ることを防ぐことが可能になりました。
しかし、当たり前ですが AilmentStatus 型でアノテーションすると、AilmentNumber が number 型になってしまいます。
この時以下のように satisfies を使うことで求めていたことができます。
const ailmentStatus = {
Burn: 0,
Freeze: 1,
Paralysis: 2,
Poison: 3,
Sleep: 4,
Fainting: 99,
Terastal: 10000, // 'Terastal' は型 'AilmentStatus' に存在しません。
} as const satisfies AilmentStatus
type AilmentNumber = ValueOf<typeof ailmentStatus>
// AilmentNumber は number 型ではなく、ailmentStatus のバリューのユニオン型になる
satisfies によって型のチェックを行いつつ、推論結果を使って AilmentNumber 型も ailmentStatus のバリューのユニオン型のまま使うことができます。
まとめ
個人的に既存のプロダクトで satisfies を使って嬉しかったところを書かせていただきました。
他にもまだまだあるかもしれませんので、もう少し深ぼってキャッチアップしていこうと思いました。
Discussion