Vinyl

satisfies演算子は積極的に使おう

はじめに

TypeScriptのsatisfies演算子が型アノテーションとどう違うか、また実際の使い所が分かってなかったので勉強がてらまとめます。

satisfies演算子とは

Version4.9で導入された機能で、式が型にマッチするかどうかをチェックしすることができます

Announcing TypeScript 4.9 Beta - TypeScriptToday we’re announcing our beta release of TypeScript 4.9! To get started using the beta, you can get it through NuGet, or- use npm with the following command: npm install -D typescript@beta You can also get editor support by Downloading for Visual Studio 2022/2019 Following directions for Visual Studio Code. Here’s a quick list of […]favicon icondevblogs.microsoft.com

下記の型とオブジェクトを元に確認していきます。

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の値を取り出します。

すると型アノテーションを使用したred01string or numberの配列として推論されますが、red02numberの配列として推論が保持されていることが確認できます。

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防止を同時に行うfavicon iconzenn.dev