🗝️

【重要】TypeScript 4.9に関する記事

2022/11/17に公開

はじめに

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に対する等価性チェック

余談

本記事が初めてなので、プログラミング言語に関するアプデ情報をまとめるのは苦労した。公式ドキュメントを読み返してもわからないことや腑に落ちないことが多く、「これはどうやってまとめようか...」と考えることがあった。

とりあえず文章の形にしてこの場で共有できたことが幸いである。

参考サイト

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

脚注
  1. アクセサとは、オブジェクト指向プログラミングでオブジェクト内部のメンバ変数に外部からアクセスするために用意されたメソッドを意味する。メンバ変数をオブジェクト内部に隠蔽し、外部から直接参照させないようにするために用意される。詳細はe-wordsを確認。 ↩︎

  2. Microsoftからのアナウンスでは、これをde-sugarと表現している。 ↩︎

  3. JavaScriptの等価演算子に関する基礎知識は、Qiitaの記事「JavaScript 忘れがちな === と == の違い」を確認。 ↩︎

  4. 浮動小数点数に関する詳細は、Wikipediaのページを確認。多少高校数学の知識が要るので注意が必要である。 ↩︎

GitHubで編集を提案

Discussion