satisfies演算子は積極的に使おう
はじめに
TypeScriptのsatisfies
演算子が型アノテーションとどう違うか、また実際の使い所が分かってなかったので勉強がてらまとめます。
satisfies演算子とは
Version4.9で導入された機能で、式が型にマッチするかどうかをチェックしすることができます
下記の型とオブジェクトを元に確認していきます。
type Colors = 'red' | 'green' | 'blue'
type Palette = {
[key in Colors]: string | number[]
}
const palette01= {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255]
}
以下のように、式 satisfies
型 といった形式で使うことが出来ます。
また、型チェックもしてくれます。
// satisfies演算子
const palette = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255]
} satisfies Palette
// Error
const errorPalette = {
red: [255, 0, 0],
green: '#00ff00',
white: [0, 0, 255]
} satisfies Palette
では型アノテーションとはどう違うのでしょうか
実際に先ほどの型とオブジェクトを元に確認してみると、satisfies
同様に型チェックもされます。
// 型アノテーション
const palette: Palette = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255]
}
// Error
const palette: Palette = {
red: [255, 0, 0],
green: '#00ff00',
white: [0, 0, 255]
}
型チェックをするといった範囲にとどまれば、違いはなさそうですが、satisfies
は型推論を保持するといった特徴があります。
では「型推論を保持する」とはどういうことなのか、先ほどの型情報とオブジェクトを元に確認します。
型アノテーションとsatisfies
で型チェックをしたpalette
オブジェクトと、そこからred
の値を取り出します。
すると型アノテーションを使用したred01
はstring or number
の配列として推論されますが、red02
はnumber
の配列として推論が保持されていることが確認できます。
const palette01: Palette = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255]
}
const palette02 = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255]
} satisfies Palette
const red01 = palette01['red']
const red02 = palette02['red']
型アノテーションは必要なのか
先ほどのケースで確認した限り、satisfies
は型チェックができる上にさらにその推論結果を保持してくれるようです。
正直先程のような型の整合性をチェックするといった場面では、型アノテーションの優位性はないと考えます。
しかし、型が推論できない場面、つまりリテラルや定数以外の値で型を指定したい場合には、型アノテーションを使用する必要があります。satisfies
はあくまでも既存のオブジェクトや値に対して型をチェックするために使用されるため、新しい変数宣言には適していません。
type Item = {
id: number;
name: string;
};
// NG
let myItem satisfies Item
// OK
let myItem:Item
satisfiesを活用する
先ほどの例から、単純な型チェックの際にもsatisfies
が使える場面では、型アノテーションではなく、satisfies
を使った方がいいということがわかりました。
次はsatisfies
がどのような場面で役立つか具体的なユースケースを考えていきます。
曖昧な型のチェックを厳密にできる
Record<string, unknown>
のような曖昧な型定義を使う場合でも厳密に型のチェックができます。
type ColorPalette = Record<string, unknown>
const palette01 = {
white: '#fffff',
red: [0, 0, 255]
} satisfies ColorPalette
const palette02: ColorPalette = {
white: '#fffff',
red: [0, 0, 255]
}
このコードでは、paletteオブジェクトがRecord<string, unknown>
型に適合しているかをチェックしています。
palettetの値を取り出した際に、型アノテーションだと警告なくpalette02.blue
のように存在しないプロパティにアクセスできてしまいますが、satisfiesを使うことにより型推論の結果を保持できるので、存在しないプロパティへのアクセスはエラーが表示される上に、palette01.red
の配列の値にアクセスした場合もmap等の配列操作メソッドも型安全に使用が出来ます。
定数の宣言と相性が良い
as const
と併せてつかうことにより、型の安全性を保証しつつ、型推論の結果を保持することができます。
例えば以下のような定数を宣言し、export
したい場合はas const
を使用し、型のwideringを防ぎ、readonlyとするのが一般的かと思います。
export const ColorList = {
red: '#000000',
blue: '#000000',
green: '#000000'
} as const
ここでsatisfies
を併せて使うことにより、加えて型の整合性も担保することができ、より安全な定数とすることができます。
export const ColorList = {
red: '#000000',
blue: '#000000',
green: '#000000'
} as const satisfies { [key: string]: string }
さいごに
自分が確認した限りでは、satisfies
を使うことによるトレードオフやデメリットのようなものは特に見当たりませんでした。型アノテーションと比較した際も、特段コード規約等に縛りがない限りは積極的に提案してよいものだと認識することができました。
今後は積極的に使っていこうと思います。
参考
TypeScript 4.9のas const satisfiesが便利。型チェックとwidening防止を同時に行うzenn.dev