【TS ver4.9以降】satisfies Operator とは?
本記事の趣旨(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