value satisfies Type は、value が Type に合っているかをコンパイル時にチェックし、値自身のより正確な型はそのままにします。型注釈ならその正確な型を Type に置き換えてしまいますが、satisfies は型を広げずに検証します。
チェックはきちんと行われます。色が足りない、bleu のようにキーを打ち間違える、true のような値を書くと、その行でコンパイルエラーになります。TypeScript 4.9 から使え、ほかの型注釈と同じように出力されるJavaScriptからは取り除かれます。
satisfies が解決する問題
型注釈を付けると、変数の型は注釈の型そのものになります。コンパイラーはリテラルで見たものを忘れてしまいます。次の例では同じパレットに型注釈を付けていて、TypeScriptは green が文字列であることをもう知りません:
コンパイラーは次のエラーを報告します:
index.ts(11,27): error TS2339: Property 'toUpperCase' does not exist on type 'Color'.
Property 'toUpperCase' does not exist on type '[number, number, number]'.
TypeScript 4.9 より前の選択肢は、型注釈を付けてあちこちで手作業で絞り込む(typeof palette.green === "string")か、注釈をやめてチェックを失うかのどちらかでした。satisfies なら両方が手に入ります。: Record<ColorName, Color> を、閉じかっこのあとの satisfies Record<ColorName, Color> に変えれば実行できます。
satisfies、型注釈、as の比較
同じ設定オブジェクトを3通りに書いてみます:
as は足りない lang を通してしまい、asserted.lang は型が string なのに実行時には undefined です。ほかの2行から lang を消すと、どちらも TS2741 Property 'lang' is missing in type ... で失敗します。
型注釈 const x: T = v | アサーション v as T | v satisfies T | |
|---|---|---|---|
| 足りないプロパティ | エラー | 通る | エラー |
| 余分なプロパティ(オブジェクトリテラル) | エラー | 通る | エラー |
| プロパティの型の間違い | エラー | 型に重なりがない場合だけエラー | エラー |
その後の x の型 | T | T | v の推論された型 |
リテラル型("dark"、8080) | T に広げられる | T に広げられる | T が許す範囲で保たれる |
Record<string, ...> のキー | 任意の文字列(打ち間違いもコンパイルできる) | 任意の文字列 | 書いたキーだけ |
| 実行時の効果 | なし | なし | なし |
目安として、変数に宣言した型を持たせたいとき(再代入する値、公開API)は型注釈を使い、チェックはしたいが値自身の型のほうが役に立つときは satisfies を使います。
オブジェクトリテラルの間違いを捕まえる
satisfies は余分なプロパティのチェックを含む代入可能性のチェックを完全に行うので、キーの打ち間違いはエラーになります:
type Route = { path: string; method: "GET" | "POST" };
const home = { path: "/", metod: "GET" } satisfies Route;
// error TS2561: Object literal may only specify known properties, but 'metod' does not exist in type 'Route'. Did you mean to write 'method'?
このチェックは、型注釈と同じようにリテラルに文脈上の型も与えます。これには2つの意味があります。対象の型がリテラル型を期待していれば、文字列リテラルはリテラル型のまま保たれます。{ path: "/", method: "GET" } satisfies Route は method: "GET" を持ちますが、同じオブジェクトを型注釈なしで書くと method: string と推論されます。そして、コールバックの引数の型が対象の型から推論されます:
Record のキーがわかったままになる
よくある使い方はルックアップテーブルです。Record<string, T> と型注釈を付けると、どんな文字列も正しいキーになり、打ち間違いもコンパイルできて、実行時に undefined が返ります。satisfies なら値は T と照らし合わせてチェックされつつ、変数の型には書いたキーだけが並びます:
keyof typeof endpoints が役に立つのは、キーが残っているからです。型注釈ならただの string になります。
決まったキーの集合を必須にするには、ユニオン型をキーにした Record を満たすようにします。satisfies Record<"dev" | "prod", string> なら、足りない prod は TS2741 で、知らない staging は TS2353 で報告されます。
as const satisfies
as const と satisfies は組み合わせられます。as const を先に書きます。値を奥まで readonly のリテラル型にし、そのあと satisfies がその正確な値をチェックします。
各ルートは Route と照らし合わせてチェックされ(method: "PUT" ならエラー)、リテラル型のタプルはそのまま使えるので、Path は実際のパスのユニオン型になります。as const の配列は readonly なので、対象の型には readonly Route[](または ReadonlyArray<Route>)を使います。
設定オブジェクト
satisfies がいちばん力を発揮するのは設定です。形は正しくなければならず、ほかの場所のコードは正確な値を必要とします。
production のエントリーを忘れる、logLevel を打ち間違える、logLevel: "verbose" と書く、といった間違いはコンパイラーが正確な行を指摘します。同じパターンは *.config.ts ファイルにも向いています。export default { ... } satisfies SomeConfig と書けばファイル全体がチェックされ、export したオブジェクトはリテラルの値を保ちます。
satisfies を使わないほうがよい場面
- 変数を再代入する。
let cfg = { port: 3000 } satisfies { port: number | string }とするとcfgの型は{ port: number }になるので、あとのcfg = { port: "80" }は失敗します(TS2322)。変更するつもりの変数には型注釈を付けましょう。 - あえて宣言した型にしたい。 関数の戻り値や、APIの一部である export した定数では、型注釈の型が約束です。正確なリテラル型が外に漏れると、あとの変更が破壊的変更になりかねません。
- 値がリテラルではない。
satisfiesが役立つのはオブジェクトリテラルと配列リテラルです。変数や呼び出しの結果に使っても普通の代入可能性のチェックになるだけで、それなら型注釈で十分です。
よくある質問
TypeScriptの satisfies は何をしますか?
expression satisfies Type は、式が Type に代入できるかをコンパイル時にチェックし、足りないプロパティ、余分なプロパティ、値の型の間違いを報告したうえで、式自身の推論された型はそのままにします。型注釈の安全性と推論の正確さの両方が得られます。JavaScriptの出力からは消えます。
satisfies と型注釈の違いは何ですか?
どちらも値をチェックします。型注釈(const x: T = ...)はそのあと変数に型 T を与え、コンパイラーが値について知っていたこと(リテラル型、各プロパティがユニオン型のどのメンバーか、どのキーがあるか)を忘れます。satisfies T は推論された型を保つので、x.someKey が存在することがわかり、文字列を持つ string | number のプロパティは string 型になります。
TypeScriptの satisfies と as の違いは何ですか?
as はアサーションで、型を上書きしてほとんど何もチェックしないので、足りないプロパティにも気づけません。satisfies はチェックで、値は本当にその型に合っていなければならず、値自身の推論された型は保たれます。どちらでもコンパイルできる場面では、satisfies のほうが安全です。
as const satisfies とはどういう意味ですか?
両方を適用します。as const が値を奥まで readonly のリテラル型にし、そのあと satisfies がその結果を型と照らし合わせてチェックします。as const を先に書きます: const routes = [...] as const satisfies readonly Route[];。変数は正確なリテラル型をあとで使えるように保ち、間違ったエントリーはコンパイルエラーになります。
satisfies はどのバージョンのTypeScriptで追加されましたか?
2022年11月にリリースされたTypeScript 4.9 です。消去できる普通の構文なので、Node の組み込みの型除去でも動き、現在のTypeScriptのすべてのバージョン(7 を含む)が対応しています。