はじめに
皆さんこんにちは。新卒2年目エンジニアの細川です。今回はTypeScriptのsatisfies
についてまとめたいと思います。satisfies
単体での使い道や、as const
と組み合わせた使い方についてもまとめているので、参考になれば幸いです。
satisfiesとは
satisfies
はTypeScript 4.9にて追加されました。
公式のドキュメントによると、「式の結果の型を変更せずに、式の型が何らかの型と一致するかどうかを検証できます。」とのことです。言葉だけではわかりづらいので、実際のコードを見てみましょう。
使い方
まず、以下のようなコードを考えてみます。
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
// ^^^^ タイポ! blueの打ち間違い
};
// redでは配列のメソッドを使いたい
const redComponent = palette.red.join();
// greenではstringのメソッドを使いたい
const greenNormalized = palette.green.toUpperCase();
上記の場合、bleu
というタイポを防ぐための手段として、型注釈をするという方法があります。
以下のように、Record型で型注釈を行います。Record型ってなに?って方はこちらの記事を確認してみてください。
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
// paletteはred, green, blueという名前のstring | RGB型のプロパティを持つオブジェクト型
const palette: Record<Colors, string | RGB> = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
// ~~~~ タイポしてるので、エラーが出る
};
// paletteのredはstringの可能性もあるため、配列のメソッドが使えない
const redComponent = palette.red.join();
型注釈をしてしまうと、型が変わってしまい、上記のように不都合が出る場合があります。
この場合、配列のメソッドを使うためには、型を絞り込んだりする手間が発生します。
このような場合、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.join(); // 配列型
const greenNormalized = palette.green.toUpperCase(); // string型
余剰プロパティのチェック
satisfies
を使うと、余剰プロパティチェックを行う際にも利用することができます。
余剰プロパティチェックというのはオブジェクトリテラルを直接代入する際に、余剰プロパティがチェックされるものです。気になる方はこちらを参考にしてみてください。
type Colors = "red" | "green" | "blue";
const favoriteColors: Record<Colors, unknown> = {
"red": "yes",
"green": false,
"blue": "kinda",
"platypus": false
// ~~~~~~~~~~ platypusはColorsに含まれていないので、余剰プロパティとなり、エラーが出る。
};
// favoriteColorsの各プロパティはunknown型になってしまうので、boolean型に代入できない
const g: boolean = favoriteColors.green;
このように、余剰プロパティをチェックする際にも型が変更されてほしくない場合もあるかと思います。
ちなみに余談ですが、余剰プロパティのチェックは、リテラルの直接代入の時しかされないので、その点には注意が必要です。
const favoColosrs = {
"red": "yes",
"green": false,
"blue": "kinda",
"platypus": false
};
// 変数での代入の場合は、余剰プロパティのチェックはされない
const favoriteColors: Record<Colors, unknown> = favoColosrs
satisfies
を使った場合の余剰プロパティチェックの仕方は以下のようになります。
type Colors = "red" | "green" | "blue";
const favoriteColors = {
"red": "yes",
"green": false,
"blue": "kinda",
"platypus": false
// ~~~~~~~~~~ platypusは余剰プロパティと判断されてエラー
} satisfies Record<Colors, unknown>;
// 型の情報がそのまま保持される
const g: boolean = favoriteColors.green;
プロパティ名のタイポや余剰プロパティのチェックはもちろん、Record
型に型情報を正しく設定することで、各プロパティの型が正しいかどうかも確かめられるので、便利そうですね。
不要な変数宣言の回避
satisfies
の他の使い道として不要な変数宣言を回避するのにも使えるかなと思います。
試しに以下のようなコードを書いてみます。
type User = {
name: string
age: number
}
type createUser = (name: string, age: number) => User;
const createUser: createUser = (name, age) => {
return {
name,
age,
hoge: "hoge"// 余剰プロパティをエラーにしたい
}
};
このcreateUser
で余分なプロパティが無いかなどを確認するには以下のようにコードを書き換える必要があります。
const createUser: createUser = (name, age) => {
const user: User = {
name,
age,
hoge: "hoge" // 余剰プロパティチェックがなされ、エラーとなる
}
return user
};
このように書き換えると、型を正しくチェックすることができます。
しかし、型のチェックのために処理を変えるというのは、あまり好ましくない場合もあるかもしれません。
皆さん、ご存知の通り、TypeScriptはJavaScriptにコンパイルされます。
一つ目と二つ目のcreateUser
のコンパイル結果を比べてみると以下のようになります。
// 1つめ
const createUser = (name, age) => {
return {
name,
age,
hoge: "hoge" // 余剰プロパティをエラーにしたい
};
};
// 2つめ
const createUser = (name, age) => {
const user = {
name,
age,
hoge: "hoge" // 余剰プロパティチェックがなされ、エラーとなる
};
return user;
};
型チェックのために無駄な変数の宣言が発生してしまっています。
このような場合、satisfies
を使うことで、無駄に変数宣言をせずに、型のチェックを行うことができます。
const createUser: createUser = (name, age) => {
return {
name,
age,
hoge: "hoge"// 余剰プロパティチェックがなされ、エラーとなる
} satisfies User
};
このようにすると、余剰プロパティのチェックを行うことができます。
また、このコードのJavaScriptへのコンパイル結果を見てみると、以下のようになります。
const createUser = (name, age) => {
return {
name,
age,
hoge: "hoge" // 余剰プロパティチェックがなされ、エラーとなる
};
};
このように、コンパイル後のコードを変えることなく、型のチェックを行えるようになります。
JavaScriptチックな書き方をしたい場合も使えそうですね!
satisfies + as const
satisfies
とas const
を組み合わせた使い方について、こちらのブログで同期の龍ちゃんに解説を頼まれていたため、書いてみます!
今まで述べてきたように、satisfies
を使うと型のチェックを行うことができます。また、as const
は使うことで、readonlyとすることができます。
龍ちゃんのブログの例を考えてみましょう。
as const satistfies
は以下のように使われています。
const colorCode = {
main: 'bg-blue-400',
error: 'bg-red-400',
} as const satisfies Record<string, string>;
分解して考えてみましょう。satisfiesだけの場合は、以下のように型のチェックは行われますが、プロパティを書き換えることができます。
const colorCode = {
main: 'bg-blue-400',
error: 'bg-red-400',
second: 1234,// number型は許容されないのでエラー
}satisfies Record<string, string>;
colorCode.main = "hoge"// 書き換え可能
as const
だけの場合は、readonlyとなりますが、違う型のプロパティでも定義できてしまいます。
const colorCode = {
main: 'bg-blue-400',
error: 'bg-red-400',
second: 1234,// errorにならない
} as const;
colorCode.main = "hoge" // readonlyのためエラー
このように、satisfies
とas const
を組み合わせて使うことで、readonlyにしつつ、型チェックを行うことができます。
また、as const
の他の効果として、型のwideningを防げるというのもあります。
as const
をつけない場合、以下のように各プロパティの型の推論結果はstringとなっています。
as const
をつけることで、以下のように、推論結果でより詳細に中身を確認することができます。詳細は型のwideningについて調べてみて下さい。
as const
をつけると型の推論をより細かくできますが、as const
だけでは型をチェックすることができません。また、型注釈をしてしまうと、以下のように推論結果が失われてしまいます。
そのため、型の推論結果を保ちつつ(元の型を変えずに)、型チェックができるsatisfies
と、as const
を組み合わせるという使い方が、良しとされているようです。
まとめ
satisfies
は、型を変えずに、型チェックができる!- JavaScriptチックに書きたいときにも使える
- 定数の宣言の時は
satisfies
+as const
を利用する!
おわりに
今回はTypeScriptのver.4.9で追加されたsatisfies
についてまとめてみました。
業務ではあまり使ってなかったのですが、今回まとめてみてメリットが結構あることが分かったので、今後は積極的に活用していきたいと思います。ほかにもこんなところで使えるよ!みたいなことを見つけたら改めて記事にしようと思います!