TypeScript4.9で追加されたsatisfiesを理解する
TypeScript4.9で追加されたsatisfies
演算子がなかなかに使えそうだと言うことで、理解するために色々まとめてみました
そもそも
なぜsatisfies
演算子が必要になったのかと言うと、、、
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の開発者はしばしばジレンマに直面する。ある式がある型に必ずマッチするようにしたいが、推論のためにその式の最も具体的な型も残しておきたいのである。
具体的に何をするのか
これだけ見ても分からないので、詳しくコードを書いて確かめてみましょう
TypeScriptのドキュメントの例に倣って色を管理するオブジェクトを作ってみましょう
type RGBA = {
r: number;
g: number;
b: number;
a: number;
};
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255, 1],
white: {
r: 255,
g: 255,
b: 255,
a: 1,
},
};
TypeScriptの例からちょっとアレンジしました
このようにred
、green
、blue
、white
をキーにとるパレットオブジェクトを作成しました
これだとなんの型の強制力もないので、もしキーを間違って
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255, 1], // blueをbleuとタイポしてしまった
white: {
r: 255,
g: 255,
b: 255,
a: 1,
},
};
としてもなんのエラーも出ません
これだとこの定数を使用するプログラマの人が困るので、ミスを避けるために何かしらの型の強制力をつけたいですよね
といってもどう型をつければいいでしょうか?
単純に型を作成する
keyは分かっているが、valueは文字列から数字のタプル(これも3個のものから4個のものもあります)、さらにオブジェクトもvalueとしてとり得る可能性があります
愚直に
type Palette = {
red: [number, number, number];
green: string;
blue: [number, number, number, number];
white: RGBA;
}
としてもいいですが、もしred
に透明値が必要になり、HEXやRGBAにしたくなったら大変です
また、今はキーが4つですがこれが増えたらその都度Palette
型も変更しなくてはいけません
RecordとUnionの併用
じゃあ次はちょっとスマートにしてみましょう
type Colors = "red" | "green" | "blue" | "white";
type Palette = Record<
Colors,
[number, number, number] | string | [number, number, number, number] | RGBA
>;
キーをUnionとして型で定義し、Recordとしてまとめました
これなら新しい色をPaletteに追加しようとしてもColors
に新しいキーを追加すればOKですし
新しい色指定の型を追加しようとしてもRecordの第二GenericsのUnionに新しい型を追加すれば済みます
ですが、これはこれで問題があります
それは使用時に型がUnionになるという問題です
例えば、red
のvalueを使用したい時、上のような型を使用すると以下のような感じになります
const palette: Palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255, 1],
white: {
r: 255,
g: 255,
b: 255,
a: 1,
},
};
palette.red // string | [number, number, number] | [number, number, number, number] | RGBA
palette.red
の型がUnionのまま定まらず、使用するごとに対象の型へキャストしなければいけません
これだと型の安全性などあってないようなものです
もしred
を間違ってRGBA
型に変換しようものなら事故ります
satisfies
ここでsatisfies
演算子の出番です
今回の例のように、オブジェクトをある型に一致させるように強制はしたいが、その値に関しては型推論をしてよしなにして欲しい場合にsatisfies
演算子は効果を発揮します
では早速使ってみましょう
type Colors = "red" | "green" | "blue" | "white";
type Palette = Record<
Colors,
[number, number, number] | string | [number, number, number, number] | RGBA
>;
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255, 1],
white: {
r: 255,
g: 255,
b: 255,
a: 1,
},
} satisfies Palette;
こんな感じで、定数の後ろにsatisfies <型>
をつけることで使用できます
使ってみた結果が以下のような感じ
ちゃんと型推論された結果がvalueの型として適用されているのがわかりますね
これをもし間違ってblue
をbleu
にしようものなら
このようなエラーが
間違ってRGBAのa
の部分を文字列にしても
のようなエラーできちんとプログラマに知らせてくれます
これで、使用時にもストレスなくかつ、型のことを考慮せず値を使用できるようになりました
まとめ
satisfies
は先の例題のようにある程度曖昧な型を強制させつつ、かつ型推論の結果も使用するという痒い所に手が届いたものになっているかなと思います
型注釈ではvalueの型がUnionになって面倒だった場面が割とあったので今回のこのsatisfies
演算子の追加は個人的にかなり嬉しい変更となりました
Discussion