【重要】TypeScript 4.9に関する記事
はじめに
2022年11月15日にMicrosoftからTypeScript 4.9に関する情報が発表されたので、新機能や改善点を簡潔に解説する。
statisfies
演算子
TypeScriptである式がある型に一致することを保証するときに、推論のためにその式の最も具体的な型も残しておきたい場合がある。例えば、以下のようなコードで考える。
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
// ^^^^ 単なるタイプミス
};
// 'red'にあるメソッドを使ってみたい
const redComponent = palette.red.at(0);
// 'green'にあるメソッドを使う。
const greenNormalized = palette.green.toUpperCase();
bleu
と書いてあることに注意しよう。この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]
// ~~~~ このタイプミスが自動で訂正される。
};
// しかし、この変数を定義する式ではpalette.redの型が文字列として設定されている。
const redComponent = palette.red.at(0);
satisfies
演算子を使うと、式の型を変更することなく式の型がある型と一致するかどうかを検証できる。たとえば、palette
のすべてのプロパティがstring | number[]
と互換性があることを検証するために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();
statisfies
は、さまざまなエラーを検出できる。たとえば、あるオブジェクトがある型のキーをすべて持っていて、それ以上持っていないことを確認できる。
type Colors = "red" | "green" | "blue";
// favoriteColorsがColorsのキーを正確に持っていることを確認する
const favoriteColors = {
"red": "yes",
"green": false,
"blue": "kinda",
"platypus": false
// ~~~~~~~~~~ "platypus"はColorsには存在しないので、エラーが発生する
} satisfies Record<Colors, unknown>;
// red、green、blueそれぞれの各特性に関する情報はすべて保持される
const g: boolean = favoriteColors.green;
プロパティ名が何らかの形で一致しているかどうかは気にしないものの、各プロパティの型は気にするという場合もあるだろう。その場合、オブジェクトのプロパティ値がすべて何らかの型に適合していることを確認できる。
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();
in
演算子による非リスト化プロパティの絞り込み
開発者としては、実行時に完全にはわからない値を扱う必要があることがよくある。実際、サーバからの応答や設定ファイルの読み取りなど、プロパティが存在するかどうかわからないことがしばしばある。JavaScriptにおけるin
演算子は、オブジェクトにプロパティが存在するかどうかを確認できる。
以前は、TypeScriptはプロパティを明示的に列挙していない型を絞り込むことができた。
interface RGB {
red: number;
green: number;
blue: number;
}
interface HSV {
hue: number;
saturation: number;
value: number;
}
function setColor(color: RGB | HSV) {
if ("hue" in color) {
// 'color' now has the type HSV
}
// ...
}
ここでは、RGB
タイプでは色相が記載されておらず絞り込まれており、HSV
タイプが残っている。
しかし、あるプロパティを示す型がない場合はどうなるだろうか。このような場合、言語は問題ではない。JavaScriptで次のような例を見てみよう。
function tryGetPackageName(context) {
const packageJSON = context.packageJSON;
// オブジェクトをふくむかどうかを確認
if (packageJSON && typeof packageJSON === "object") {
// 文字列の"name"というプロパティがあるかどうかを確認
if ("name" in packageJSON && typeof packageJSON.name === "string") {
return packageJSON.name;
}
}
return undefined;
}
TypeScript 4.9では、in
の使用方法に関するいくつかのチェックも強化され、左側がstring|number|symbol
の型に割り当て可能であること、右側がオブジェクトに割り当て可能であることが確認されている。これは、有効なプロパティキーを使っているか、また誤ってプリミティブをチェックしていないかを確認するのに役立つ。
クラス内の自動アクセサ
TypeScript 4.9はECMAScriptの次期機能である自動アクセサ[1]をサポートしている。自動アクセサはクラスのプロパティと同じように宣言されるが、予約語accessor
で宣言される点が異なる。
class Person {
accessor name: string;
constructor(name: string) {
this.name = name;
}
}
これらの自動アクセッサは、プライベート変数を持つget
およびset
アクセッサの機能を1つに格納[2]しているのです。
class Person {
#__name: string;
get name() {
return this.#__name;
}
set name(value: string) {
this.#__name = name;
}
constructor(name: string) {
this.name = name;
}
}
NaN
に対する等価性チェック
JavaScriptの開発で生じる大きな問題の1つに、組み込みの等値演算子[3]によるNaN
のチェックが考えられる。背景として、NaN
は "Not a Number"(和訳:数字ではない)を意味する特殊な数値だ。NaNと等しいものはない。
console.log(NaN == 0) // false
console.log(NaN === 0) // false
console.log(NaN == NaN) // false
console.log(NaN === NaN) // false
ところが、少なくとも対称的にはすべてが常にNaN
と等しいわけではない。
console.log(NaN != 0) // true
console.log(NaN !== 0) // true
console.log(NaN != NaN) // true
console.log(NaN !== NaN) // true
これは技術的にはJavaScript固有の問題ではなく、浮動小数点数[4]を含むどの言語でも同じ動作をする。しかし、JavaScriptの主要な数値型は浮動小数点数であり、JavaScriptの数値解析はしばしばNaN
になることがある。そのため、NaN
に対するチェックはかなり一般的になっており、正しい方法はNumber.isNaN
を使うことだが、先ほど述べたように、多くの人が誤って代わりに someValue === NaN
でチェックしてしまう。
TypeScriptは現在、NaN
との直接比較をエラーとし、代わりにNumber.isNaN
のバリエーションを使用することを推奨している。
function validate(someValue: number) {
return someValue !== NaN;
// ~~~~~~~~~~~~~~~~~
// error: This condition will always return 'true'.
// Did you mean '!Number.isNaN(someValue)'?
}
Microsoftからのアナウンスによると、この変更点は現在TypeScriptがオブジェクトや配列のリテラルに対する比較でエラーを出しているのと同様に、初心者のエラーを厳しくキャッチするのに役立つらしい。
おわりに
今回の記事では、Microsoftからのアナウンスをもとに、TypeScript 4.9における新機能の説明を文法面を中心に簡潔に解説した。
【注目するべき機能】
-
statisfies
演算子 -
in
演算子による非リスト化プロパティの絞り込み - クラス内の自動アクセサ
-
NaN
に対する等価性チェック
余談
本記事が初めてなので、プログラミング言語に関するアプデ情報をまとめるのは苦労した。公式ドキュメントを読み返してもわからないことや腑に落ちないことが多く、「これはどうやってまとめようか...」と考えることがあった。
とりあえず文章の形にしてこの場で共有できたことが幸いである。
参考サイト
-
アクセサとは、オブジェクト指向プログラミングでオブジェクト内部のメンバ変数に外部からアクセスするために用意されたメソッドを意味する。メンバ変数をオブジェクト内部に隠蔽し、外部から直接参照させないようにするために用意される。詳細はe-wordsを確認。 ↩︎
-
Microsoftからのアナウンスでは、これを
de-sugar
と表現している。 ↩︎ -
JavaScriptの等価演算子に関する基礎知識は、Qiitaの記事「JavaScript 忘れがちな === と == の違い」を確認。 ↩︎
-
浮動小数点数に関する詳細は、Wikipediaのページを確認。多少高校数学の知識が要るので注意が必要である。 ↩︎
Discussion