🪐

Typescript 4.9 の satisfies Operator が気になる

2022/09/28に公開約4,400字2件のコメント

🌼 はじめに

最近 TypeScript 4.9 Beta のお知らせがありましたね!

https://devblogs.microsoft.com/typescript/announcing-typescript-4-9-beta/

その中で新しく登場したsatisfiesがとても気になったのでざっと整理してみたいと思います。

1. Typescript のジレンマ

1番最初の文書としてこういう話が出てきます。

TypeScript developers are often faced with a dilemma: we want to ensure that some expression matches some type, but also want to keep the most specific type of that expression for inference purposes.

TypeScript経験者なら一回は出会ったことある状況かもしれません。サンプルコードで見てみましょう。

// 各プロパティは string もしくは RGB タプル型
const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255]
//  ^^^^ bleu に注意!タイポしてる!
};

// 'red'には配列メソッドを使えるように使いたい
const redComponent = palette.red.at(0);

// また'green'には文字列メソッドを使えるように使いたい
const greenNormalized = palette.green.toUpperCase();

bleuというタイポがありますね。このミスはpaletteに型アノテーションを使うことでチェックできますが、そうしたら各プロパティに関する情報が失われてしまいます。

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]
//  ~~~~ タイポが正しく検出されるようになったよ!
};

// でもここで好ましくないエラーが出てしまう
// Property 'at' does not exist on type 'string | RGB'.
// Property 'at' does not exist on type 'string'.
const redComponent = palette.red.at(0);

palette.redが本当は配列なのに型定義がstring | RGBになったため、配列メソッド使用のときエラーが出てます。

こういうとき「paletteのキーはColorsにマッチしてほしいけど(matches some type)、メソッド使用などのためにバリューにおける正確な型の推論は残してほしい(keep the most specific type)!」と思っちゃいますよね。似たようなケースも結構あると思います。

2. satisfies Operator

Typescript 4.9 で新しく登場したsatisfiesを使ったら式の型を変更することなく、式の型がある型と一致するかどうかをチェックすることができるらしいです。

実際の使い方はこちらです。

type Colors = "red" | "green" | "blue";

type RGB = [red: number, green: number, blue: number];

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255]
//  ~~~~ タイポが検出される!
} satisfies Record<Colors, string | RGB>;

// どちらのメソッドも相変わらず使える!
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();

satisfiesを使うとpaletteのすべてのプロパティがstring | number[]型と互換性があるかどうかを見るようです。それで互換性があると判断したら推論を残す感じではないかと思います。

3. satisfies で解決できる問題

上で話したジレンマ状況以外にもsatisfiesで解決できる問題がいくつか紹介されてました。

例えばオブジェクトに型定義していないプロパティは持たせたくない場合、型ノーテーションを使ったら余剰プロパティチェックで探知きます。

type Colors = "red" | "green" | "blue";

const favoriteColors: Record<Colors, unknown> = {
    "red": "yes",
    "green": false,
    "blue": "kinda",
    "platypus": false //「"platypus" というのは 'Colors' にないよ」というエラー
};

// Type 'unknown' is not assignable to type 'boolean'
const g: boolean = favoriteColors.green;

余剰プロパティチェックが何かわからない方はこの記事をご参考ください。
https://zenn.dev/luvmini511/articles/2cd39b6cffa08c

不要なプロパティを探知できたのは良いですが、プロパティの型が全部unknownになったのでgの型ノーテーションでエラーになってますね。

こういうときもsatisfiesが役立つようです。

type Colors = "red" | "green" | "blue";

const favoriteColors = {
    "red": "yes",
    "green": false,
    "blue": "kinda",
    "platypus": false //「"platypus" というのは 'Colors' にないよ」というエラー
} satisfies Record<Colors, unknown>;

// 'red'、'green'、'blue'の各プロパティに関する情報はすべて保持される
const g: boolean = favoriteColors.green;

favoriteColors.greenboolean型だという推論が残ってるので、gboolean型に宣言してもエラーにならないんんじゃないかなと推測してみます。

また、各プロパティが何らかの型に沿ってるかどうかチェックすることもできるようです。

type RGB = [red: number, green: number, blue: number];

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0]
    //    ~~~~~~ エラー!
} satisfies Record<string, string | RGB>;

// 各プロパティの情報は残ってる
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();

ちゃんと指定した型通りかどうか担保できることがいいですね。

もっと多くの使用例はイシューとプルリクで確認できます。

https://github.com/microsoft/TypeScript/issues/47920
https://github.com/microsoft/TypeScript/pull/46827

🌷 終わり

まだsatisfiesを Typescript Playground で試せないので詳細まではわかってませんが、使える場面があるんじゃないかなと思いました。リリースされたら使ってみたいですね!

+2022.09.29 追記
と、思いましたが既にplaygroundを準備してくれた方がいらっしゃるという情報をコメントでいただきました。色々試してみたいと思います!
https://www.typescriptlang.org/play?ts=4.9.0-dev.20220921#code/C4TwDgpgBAwg9gGzgJwM5QLxQETIgE2ygB8cBzPCAOyNOwCMEBXCbAbgCgPRIoAlAOIAhTFADaefAC4oVJgFt6EZABooFCNRlzFytYxbaFS5AF1OHAMZwqqYFDABDBBGDBoWAN4covqJJkxACYAVhC1AAZI0xUfPw0tHABiCIiAMzTU7Fi-KEYIJkCoqGLQkNMOAHpK3wA-etqoQH6GQBKGQF2GQBkGQBxLQC-FQFUGQBiGQGiGQEB-jgBfKFRHYABLVDTZiHQ+CGtkfAAeeCQ0NTtkWaoyEn5hAD4LaqhASwZAQwZASIZAOwZAQ4ZAXoZAYYZASYZAIIZAD7dAJCagHiGB6ALQZAP7ygAkGUZWGx2fwEeDyMA2aj2LBOFxuCAAOkkOJmAAoIgBKTjWWz2BJUAByKHkzlmAC8CKJMa53DjqTjgHAAKpgSDIGCOVAQQlkoA

GitHubで編集を提案