本セッションの登壇者
セッション動画
私は鹿野壮(@tonkotsuboy_com)といいます。今はマネーフォワードビジネスカンパニーというところで経理財務プロダクト本部開発部副部長というのをやっております。

最近、「JavaScriptコードレシピ集」(技術評論社)という書籍を書いたり、「日経ソフトウェア」(日経BP)でCSSやJavaScriptの最新記事を書いております。
本日伝えたいことのまとめですが、「as const satisfies
を使うと、wideningの防止と型推論結果の保持ができる」ということです。

satisfies
でできること
まず、as const satisfies
のsatisfies
の部分なんですけども、satisfiesとは何なのかというと、これは式 satisfies 型
という形で使います。式が型にマッチするかどうか?をチェックします。TypeScript 4.9、現時点(2023/01/18)の最新バージョンでリリースされました。

satisfies
の発音ですが、カタカナで起こすのであれば「サティスファイズ」かなと思います。意味は、
- 満足させる、満たす
- satisfiyの動詞に三単現のsが付いた形
です。
satisfies
がどういうことをするかというと、まずひとつ目がたとえば
const foo = "豚骨大学" satisfies string;
と書いた場合、「豚骨大学」という文字列が、stringにマッチするかどうかをチェックしています。当然、「豚骨大学」はstring型にマッチするので、ここはOKになります。
一方、
const foo = 14 satisfies string;
のようなコードの場合は「14」がstring型にマッチしないのでNGとなり、コンパイルエラーが出ます。これがsatisfies
の動作です。

オブジェクトの場合でも同様で、type Person
という型を定義して、age
とname
がそれぞれnumberとstring型というプロパティを持っているtype Person
です。これに対して、satisfies Person
という書き方をして、my Person
というオブジェクトを定義しました。name
がstringを期待しているところに、["ラーメン", "うどん"]
という配列を入れてしまうと、当然name
はstringではないのでNGです。ここ(配列)を文字列にすると正しく通るようになります。

「これは型注釈と何が違うのか」という疑問についてですが、「推論結果を保持するかどうか」というのが答えになります。

型注釈とsatisfies
についてですが、上が型注釈、下がsatisfiesです。

たとえば、このようなColorListという型を定義したとします。key
が"red | "green" | "blue""
で、値はなんでもいいという型とします。

このgreen
に対して、map()
関数を使おうとすると、当然green
はunknown
としか注釈されていないので、配列用の関数を使うことができないわけですね。

一方で、これをsatisfies
に書き換えると、green
が正しく配列として型注釈されるので、配列用のmap()
関数が使えるようになります。

こういった違いがあります。なので「satisfies
は型チェックをしながらも、型推論結果を保持できる」というメリットがあります。

wideningの防止とreadonly化ができるas const
「satisfies
にas const
を組み合わせると便利」というのが本日の主題です。
as const
についてですが、本日のセッションを聞いている皆様であれば、ふだん使っているかと思います。
式 as const
という使い方になります。これはTypeScript 3.4で導入され、文字列や数値、真偽値といったリテラル型をwidenigしない、また、オブジェクト内のすべてのプロパティがreadonlyになり、配列リテラルの推論結果がタプル型になるという特徴があります。

wideningというのは、リテラル型がプリミティブ型に拡大するというものです。左がリテラル型、右がプリミティブ型です。

wideningが起こるケースは
const name = "田中";
と定義した場合、name
の推論結果は"田中"型ですが、
let name2 = name;
とすると、string型にwideningしてしまいます。

配列もwideningします。myArray
に["ラーメン", "うどん", "梅が枝餅"]
を定義した場合の推論結果はstringの配列です。["ラーメン", "うどん", "梅が枝餅"]
というタプル型にはならないというのがポイントです。

オブジェクトも同様で、myObject
に具体的な値を入れていたとしても、wideningしてしまいます。

しかし、このようにwideningしないと配列もオブジェクトも書き換えができないからこのような仕様になっています。ここでas const
を使うと、wideningの防止とreadonly化ができます。

たとえば、先ほどのname
の"田中"に対してas const
を設定すると、
let name2 = name;
としても”田中”というリテラル型がそのまま保持されます。

配列に対してもas const
を付けることで、["ラーメン", "うどん", "梅が枝餅"]
というタプル型に推論されます。readonlyなので、書き換えることはできません。

オブジェクトも同様です。

とくに定数をexportするときは非wideningでreadonlyの場合が多いので、積極的にas const
を使うことが多いかなと思います。

as const
とsatisfies
を組み合わせていいとこ取り!
このas const
とsatisfies
を組み合わせるというのが本日のポイントで、
as const
はwidening防止とreadonly化ができるsatisfies
は型チェックができる
これを組み合わせると、両方のいいとこ取りをした素晴らしいものができます。

サンプルをお見せします。たとえば「Person型」を作ったとします。これにマッチするオブジェクトを作ってみましょう。

まず両方とも使わない場合は、IDEの推論結果を見るとwideningしてますし、age: number
のところに文字列を入れていますが、もちろん型チェックができていません。

ここでas const
だけを付けておくと、wideningを防止できていますが、まだ型チェックができていないので、誤って文字列を入れてもチェックしてくれません。

satisfies
だけ使った場合は、正しく型チェックをしているけど、wideningしてしまいます。

両方を付けると、wideningの防止と型チェックをすることができます。

どのような場面で活用できるかというと、たとえばアプリケーションのバージョン管理定数です。これは、${number}.${number}.${number}
というテンプレートリテラルにしています。こうすることで、バージョンを130
としても通らないようにしてます。

あとは、URLの一覧を以下のように管理することができます。この場合は、値がhttps://
で始まる値に限定してas const satisfies
しています。

ほかにも、カラーコードの列挙やステータスの配列などでも使うことができます。


##まとめ - as const satisfies
でwideningの防止と型推論結果の保持ができる
まとめです。

とくに定数をexportする場合は、使う人のことを考えてas const satisfies
しておきましょう。今日の話はzennの記事でも詳しく解説していますのであわせてご覧ください。

ご清聴ありがとうございました。