🎃

【TS ver4.9以降】satisfies Operator とは?

2023/06/14に公開

本記事の趣旨(satisfies Operator)

TypeScriptのversion4.9以降にて扱えるsatisfies Operatorに関して、サンプルコードとともに解説しています。ちなみにですが、satisfies OperatorはTSのver4.9の新機能として2022/11/15に公表されているため、割と新しめの機能になります。
また、余談ですが、TSのver4.9のリリースノートではsatisfies Operatorを一番上で紹介しているので、4.9の目玉機能と言えるかもしれません。

satisfies Operatorの利点に関して

値自身の型を保持することができる

satisfies Operatorを用いると、値自身の型を保持しつつ、型制約を与えることができます。
その利点に関して、TSの型宣言と、satisfies Operatorを用いた場合をサンプルコードを用いて比較し、解説していきます。

型宣言を利用する場合

まずは、TSでの一般的な型宣言にて定数を定義する場合にて考えてみます。
こちら↓のサンプルコードにて型宣言にてnormalHashを定義してみます。

type Options = "numberFormat" | "stringFormat"
type ValueType = string | number
const normalHash: Record<Options, ValueType> = {
  numberFormat: 12345,
  stringFormat: "string",    
}

normalHashでは、Recordの値の型がValueType型になるように宣言しています。
言い換えると、normalHashの値の12345, "string"がValueType型としてtscに認識されている状態になります。
ですので、以下のようなコードをtscしますと、エラーになってしまいます。

// tscをしたときにエラーが出る
console.log(normalHash["numberFormat"] + 1)
// tscをしたときにエラーが出る
console.log(normalHash["stringFormat"].charAt(2))

実際に、TS Playgroundにてコードを書いてみると以下のようにエラーが表示されます。

上記のコードでのtsc時にエラーが起きる原因について

エラーが起きる原因についてより深く考えてみます。 もう一度、代入式をよく見てみましょう。

const normalHash: Record<Options, ValueType> = {
  numberFormat: 12345,
  stringFormat: "string",    
}

右辺を見ると、 numberFormat にはnumber型の値が入っていますし、 stringFormat にはstring型の値が入っているので、文句を言われる筋合いはないように見えます。
しかし、左辺を見てみると、 Record<Options, ValueType> という型注釈をしていますね。この型は、実質的に次のような型を表しています。

type NormalHashType = {
  numberFormat: string | number;
  stringFormat: string | number;
};

この型で縛られた normalHash 変数は、確かに { numberFormat: 12345, stringFormat: "string" } という値を受け入れ可能です。しかしそれは、 normalHash 変数が { numberFormat: number, stringFormat: string } 型になるということではないのです。
前述のようなエラーが出てしまったのは、numberFormat や stringFormat が string | number という曖昧な型のままだったのに、数値演算や文字列操作を行おうとしたためです。
左辺に明示的に型注釈を行った場合、右辺で行われた型推論の結果がほとんど左辺に引き継がれません。この挙動は嬉しいケースもありますが、今回のケースではあまり嬉しくない挙動になりました。

satisfies Operatorを利用する場合

satisfies Operatorの書き方に関して

satisfies Operatorの利点の紹介に入る前に、satisfies Operatorの書き方の紹介になります。

const hoge = fuga satisfies hogefugaのように、定数の定義(const hoge = fuga)の後ろにsatisfies hogefugaと書きます。こうすると、hogefuga型を満たすhogeを定義していることになります。
詳しくはsatisfies Operatorのドキュメントにて確認することができます。

satisfies Operatorを利用する場合

こちら↓のサンプルコードにてsatisfies Operatorを用いてsatisfiesHashを定義してみます。

type Options = "numberFormat" | "stringFormat"
type ValueType = string | number
const satisfiesHash = {
  numberFormat: 12345,
  stringFormat: "string",    
} satisfies Record<Options, ValueType>

satisfies Operatorを用いてsatisfiesHashすることで、以下のような利点があります。

  • satisfiesHashの値はValueTypeを満たす値が入ることが保証される
  • tscにはnormalHashの値の12345, "string"がそれぞれnumber型とstring型として認識される

ですので、型宣言を利用した際にtscにてエラーになってしまったコードをsatisfiesHashにてtscするとエラーなく正常な状態として認識されます。

// satisfiesHash["numberFormat"]がnumber型として認識されているため、tscをしたときにエラーが出ない
console.log(satisfiesHash["numberFormat"] + 1)
// satisfiesHash["stringFormat"]がstring型として認識されているため、tscをしたときにエラーが出ない
console.log(satisfiesHash["stringFormat"].charAt(2))

実際に、TS Playgroundにてコードを書いてみるとエラーが表示されません。

したがって、satisfies Operatorは型宣言を利用する場合で遭遇したような型エラーを回避しつつ、型制約を与えたい場合に使える、といった利点を持ちます。

satisfies Operatorを用いた型制約に関して

satisfies Operatorは、定数の定義時にsatisfiesの後ろに与えられた型を満たしていない場合は、tsc時にエラーを表示してくれます。

以下のサンプルコードのように、stringFormatにBoolean型であるtrueを入れてみると、tsc実行時にエラーになり、原因を教えてくれます。

type Options = "numberFormat" | "stringFormat"
type ValueType = string | number
// satisfiesを利用した場合、値の型がsatisfiesしていないときはtscをしたときにエラーが出る
// エラー内容:Type 'boolean' is not assignable to type 'ValueType'.
const invalidSatisfiesHash = {
  numberFormat: 12345,
  stringFormat: true,    
} satisfies Record<Options, ValueType>

実際に、TS Playgroundにてコードを書いてみるとエラーが表示されます。

Tips: as constとsatisfies Operatorを同時に利用する

以下のサンプルコードのようにas constとsatisfies Operatorを両方用いて書くことで、satisfies Operatorを利用して定義した定数をreadonlyな定数として扱うことができます。

const readonlySatisfiesHash = {
  numberFormat: 12345,
  stringFormat: "string",    
} as const satisfies Record<Options, ValueType>

実際に、TS Playgroundにてコードを書いてみるとプロパティがreadonlyになっていることを確認することができます。

最後に

個人的には、TSは型の取り扱い方法を学べば学ぶほどより便利にバグが少なく、そしてスムーズに開発を進めていける印象を受けています。TSには今回紹介したsatisfies Operatorなど、便利な機能がたくさんあるので、少しずつ勉強していけたらと思っています👍

サンプルコード

こちらが本記事で取り扱ったサンプルコードになります。

type Options = "numberFormat" | "stringFormat"
type ValueType = string | number

// -------------------------------
// -----satisfiesを利用した場合-----
const satisfiesHash = {
  numberFormat: 12345,
  stringFormat: "string",    
} satisfies Record<Options, ValueType>
// tscをしたときにエラーが出ない
console.log(satisfiesHash["numberFormat"] + 1)
// tscをしたときにエラーが出ない
console.log(satisfiesHash["stringFormat"].charAt(2))

// -------------------------------
// -----型宣言をした場合-------
const normalHash: Record<Options, ValueType> = {
  numberFormat: 12345,
  stringFormat: "string",    
}
// tscをしたときにエラーが出る
console.log(normalHash["numberFormat"] + 1)
// tscをしたときにエラーが出る
console.log(normalHash["stringFormat"].charAt(2))

// -------------------------------
// satisfiesを利用した場合、値の型がsatisfiesしていないときはtscをしたときにエラーが出る
// エラー内容:Type 'boolean' is not assignable to type 'ValueType'.
const invalidSatisfiesHash = {
  numberFormat: 12345,
  stringFormat: true,    
} satisfies Record<Options, ValueType>

// -------------------------------
// -----as const, satisfies Operatorを利用した場合-----
const readonlySatisfiesHash = {
  numberFormat: 12345,
  stringFormat: "string",    
} as const satisfies Record<Options, ValueType>
株式会社モニクル

Discussion