1. ホーム
  2. typescript

[解決済み] TypeScriptでswitchブロックが網羅的であることを確認する方法とは?

2022-05-14 17:12:04

質問

私はいくつかのコードを持っています。

enum Color {
    Red,
    Green,
    Blue
}

function getColorName(c: Color): string {
    switch(c) {
        case Color.Red:
            return 'red';
        case Color.Green:
            return 'green';
        // Forgot about Blue
    }

    throw new Error('Did not expect to be here');
}

を処理するのを忘れていました。 Color.Blue のケースを処理するのを忘れていて、コンパイルエラーが出た方がよかったと思います。TypeScriptがこれをエラーとしてフラグを立てるようにするには、どのようにコードを構成すればよいでしょうか?

どのように解決するのですか?

これを実現するために never 型(TypeScript 2.0で導入)を使用します。これは、"not" が発生してはならない値を表します。

まず最初に関数を書きます。

function assertUnreachable(x: never): never {
    throw new Error("Didn't expect to get here");
}

そして、それを default の場合(あるいは同等に、スイッチの外側で)。

function getColorName(c: Color): string {
    switch(c) {
        case Color.Red:
            return 'red';
        case Color.Green:
            return 'green';
    }
    return assertUnreachable(c);
}

この時点で、エラーが表示されます。

return assertUnreachable(c);
       ~~~~~~~~~~~~~~~~~~~~~
       Type "Color.Blue" is not assignable to type "never"

エラーメッセージは、網羅的なスイッチに入れ忘れたケースを示します! もし複数の値を入れ忘れた場合は、例えば Color.Blue | Color.Yellow .

もし、あなたが strictNullChecks を使っている場合、その return の前にある assertUnreachable を呼び出します (それ以外は任意)。

お望みであれば、もう少し凝ったこともできます。たとえば、判別済み和集合を使用している場合、デバッグのためにアサーション関数で判別プロパティを回復すると便利なことがあります。それは次のようなものです。

// Discriminated union using string literals
interface Dog {
    species: "canine";
    woof: string;
}
interface Cat {
    species: "feline";
    meow: string;
}
interface Fish {
    species: "pisces";
    meow: string;
}
type Pet = Dog | Cat | Fish;

// Externally-visible signature
function throwBadPet(p: never): never;
// Implementation signature
function throwBadPet(p: Pet) {
    throw new Error('Unknown pet kind: ' + p.species);
}

function meetPet(p: Pet) {
    switch(p.species) {
        case "canine":
            console.log("Who's a good boy? " + p.woof);
            break;
        case "feline":
            console.log("Pretty kitty: " + p.meow);
            break;
        default:
            // Argument of type 'Fish' not assignable to 'never'
            throwBadPet(p);
    }
}

これは良いパターンです。なぜなら、期待されるすべてのケースを処理したことを確認するために、コンパイル時の安全性が得られるからです。また、本当にスコープ外のプロパティ(例えば、JSの呼び出し元が新しい species を作ったJS呼び出し元がある)、有用なエラーメッセージを投げることができる。