🍷

satisfies演算子でオブジェクトを型安全に!

2023/04/27に公開

はじめに

今回は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演算子のような便利なものが他にも色々あるので、調べてみようとおもいます!

参考

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html

Discussion