[[satisfies演算子]]を使う理由について考察する。 ## Option型と問題 以下の `Option` 型を考える。 ```ts interface Option { color: string; } ``` ソースコード内で不変なOption型の値を定義してみる。 ```ts const opt: Option = { color: "red" } as const; ``` 型エラーにはならないが、`opt.color` の値は **"red"型 ではなく string型** と推論される。 ```ts const opt: Option = { color: "red" } as const opt.color // stringと推論 ``` `opt` は不変の値であるため `opt.color` は `"red"`型 と推論されてほしい。 ## stringと推論される理由と対策 string型と推論されてしまうのは、[[Widening]]によるものだ。`opt: Option` と示すことにより、たとえ `opt.color` の値がconst値だとしても、`Option`型へと可能性を広げてしまう。ならば、`Option`型の型注釈を外してしまおう。 ```ts const opt = { color: "red" } as const opt.color // "red"と推論 ``` こうすることで、無事 `"red"`型と推論される。 ## optの妥当性が検証できない 先ほどのコードには新たな問題が発生する。`opt`は実質any型となり、何でも受け付けてしまうということだ。正確に言うと、**与えられた値に従うことが型として正しい** と信じてやまない状況なのだ。 ```ts const opt = { color: 123456, // 本来ならエラーなのだが。。。 hoge: "hogehoge" // 本来ならエラーなのだが。。。 } as const opt.color // 123456と推論 ``` これはまずい。今はコード量が少なく、わざと間違えているため明らかだが、複雑なコードで意図せぬオプション値を設定してしまうと、コードのどこかで連携できなくなり、不要な問題にハマるかもしれない。 一方で `opt: Option` と型注釈をつけてしまうと、[[Widening]]によって `string` と推論されてしまう。 ## satisfies演算子を使う [[Widening]]を回避しつつ、値に対して型の妥当性を担保したい...。そんな落としどころをつけさせるのが[[satisfies演算子]]だ。 先ほどエラーとなったコードに対して、[[satisfies演算子]]を適応してみよう。不正な箇所はしっかりとエラーが表示されるようになる。 ```ts const opt = { color: 123456, // Type 'number' is not assignable to type 'string'. [2322] hoge: "hogehoge", // Object literal may only specify known properties, and 'hoge' does not exist in type 'Option'. [2353] } as const satisfies Option; opt.color; // 123456と推論 ``` となれば、心配なのは[[Widening]]だ。正しい値を入力したときに `string` ではなく `"red"` と推論されるのか...だ。 [[#stringと推論される理由と対策]]で使ったコードを今一度見てみよう。 ```ts const opt = { color: "red" } as const opt.color // "red"と推論 ``` これに[[satisfies演算子]]を適応してみる。 ```ts const opt = { color: "red" } as const satisfies Option opt.color // "red"と推論 ``` [[satisfies演算子]]の有無にかかわらず `opt.color` は `"red"` と推論された。 ## まとめ コンパイル時に値が絞り込めるとき、型定義も同様に絞り込めた方がコードは堅牢になる。特にswitch文やif文、[[型ガード (TypeScript)|型ガード]]などで[[Narrowing]]したい場合に有効だ。そのために[[as const]]を利用することは多い。 しかし、**[[as const]]の対象となる値が特定の型を満たしていることを早期に検出したいケース** において、今までは何かを犠牲にする必要があった。[[satisfies演算子]]はこのような過去の問題を解消できる機能になっている。 > [!thinking] `typeof opt`型 を定義するやり方でもいい気はする。アプローチは逆だけど。値を信じるか、型を信じるかの違いかな。 ## 参考 - [TypeScript: Documentation \- TypeScript 4\.9](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator) - [TypeScript 4\.9のas const satisfiesが便利。型チェックとwidening防止を同時に行う](https://zenn.dev/moneyforward/articles/typescript-as-const-satisfies)