satisfies演算子でオブジェクトを型安全に!
はじめに
今回はTypeScript4.9で登場したsatisfies
演算子について解説していきます。
satisfies演算子を使わない場合の問題点
TypeScriptのドキュメントからコードを引用しています。
// stringかnumberのタプルを保持するオブジェクト
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
// ここでタイポをしてしまっている
};
// palette.redはarrayなので、arrayのメソッドを使おうとしている。
const redComponent = palette.red.map(v=>v);
// palette.greenはstringなので、stringのメソッドを使おうとしている
const greenNormalized = palette.green.toUpperCase();
ここで、タイポに対してエラーを出したいので、型アノテーションを付与してみましょう。
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette: Record<Colors, string | RGB> = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
// ~~~~ タイプミスが正しく検出されました
};
しかしpalette.red
にarrayのメソッドを使用していた場所で以下のようなエラーが発生します。
const redComponent = palette.red.map(v=>v);
// プロパティ 'map' は型 'string | RGB' に存在しません。プロパティ 'map' は型 'string' に存在しません。
これは、palette.red
がRGB型だけでなく、stringの可能性もあると推論してしまっているためです。型アノテーションを付与すると、各プロパティに対しての、型推論が失われてしまうのです。
satisfies演算子で解決
上記の問題を解決するために、satisfies
演算子を使用します。先ほどのコードをsatisfies
演算子を使用して書き換えてみましょう。
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} satisfies Record<Colors, string | RGB>;
const redComponent = palette.red.map(v=>v);
オブジェクトの末尾にsatisfies Record<Colors, string | RGB>
を追加しました
これによりpalette.red
で配列のメソッドを使っても、エラーが出ることがなくなります。
satisfies演算子だとエラーにならないはなぜ?
ドキュメントには以下のように書いてあります。(翻訳しています)
新しいsatisfies 演算子を使うと、式の結果の型を変えずに、式の型がある型に一致するかどうかを検証することができます。例えば、paletteのすべてのプロパティがstring|number[]と互換性があることを検証するためにsatisfies 演算子を使用することができます:
これがどういうことかというと、「satisfies
演算子は、型が一致するかどうかを検証するだけで、元々されていた型推論はそのまま残す」ということです。
以下は、型アノテーションとsatisfies
によって推論された型です。
// 型アノテーション
const palette: Record<Colors, string | RGB>
// stisfies
const palette: {
red: [number, number, number];
green: string;
blue: [number, number, number];
}
型アノテーションでは、プロパティの値の型が絞り切れていないことがわかります。
const assertion と satisfies の組み合わせ
型アノテーションを使用した際に、オブジェクトのプロパティは代入ができてしまうと紹介しましたが、これはsatisfies
演算子を使っていても、型が同じであれば代入ができてしまいます。
palette.red = [255,255,255]
// エラーが出ない
これを制限するためにas const
を併用します。
type Colors = "red" | "green" | "blue";
type RGB = readonly [red: number, green: number, blue: number];
// readonlyを付与しています
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} as const satisfies Record<Colors, string | RGB>;
palette.red = [255,255,255]
// 読み取り専用プロパティであるため、'red' に代入することはできません。
まとめ
satisfies
演算子を使うことで、複数の型を持つオブジェクトを扱いやすくすることができるようになります。
TypeScriptは普通に使っているだけでも便利ですが、satisfies
演算子のような便利なものが他にも色々あるので、調べてみようとおもいます!
参考
Discussion