判別可能なUnion型

Union型の判別(Discriminated Union)

literal型のメンバを持つクラスがある場合、そのプロパティを使用して、Union型のメンバを判別することができます。

例として、SquareRectangleのUnionを考えてみましょう。ここではkind(特定のリテラル型)は両方のUnion型のメンバに存在しています:

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
type Shape = Square | Rectangle;

判別用のプロパティ(ここではkind)に対して、型安全なチェック(=====!=!==)またはswitchを使用すると、TypeScriptはあなたのために、そのリテラル型を持つオブジェクトの型を特定し、型の絞り込みを行います :)

function area(s: Shape) {
    if (s.kind === "square") {
        // Now TypeScript *knows* that `s` must be a square ;)
        // So you can use its members safely :)
        return s.size * s.size;
    }
    else {
        // Wasn't a square? So TypeScript will figure out that it must be a Rectangle ;)
        // So you can use its members safely :)
        return s.width * s.height;
    }
}

網羅チェック(Exhaustive Checks)

一般論として、あなたはユニオンのすべてのメンバに対して漏れなくコード(またはアクション)が存在していることを確認したいでしょう。

Circleのインスタンスが渡された場合に悪いことが起きる例:

これをチェックするには、フォールスルー(else)を追加し、そのブロックの推論された型がnever型と互換性があるかを確認するだけです。たとえば、その網羅チェックを追加すると、ナイスなエラーが発生します:

これによって、あなたは新しいケースに対応することを強制されます:

スイッチ(Switch)

ヒント:もちろん、switchステートメントでも同じことが可能です:

strictNullChecks

strictNullChecksを使用して網羅チェックを行っている場合、TypeScriptは"not all code paths return a value"というエラーを出すかもしれません。そのエラーを黙らせるには、シンプルに_exhaustiveCheck変数(never型)を返すだけです:

網羅チェックの中で例外を投げる

引数としてneverを取る関数を書くことができます(したがって、この関数はneverとして推論された変数で呼ばれた場合にのみ呼ばれます)。そして、次のように、関数の本体が実行された場合に例外を投げるように書きます。

以下に、area関数とともに使用する例を示します。

Retrospective Versioning

次のような形のデータ構造があるとします。

そして、DTOをさまざまな場所で使用した後に、nameという名前は良くない選択だったことに気が付いたとします。このような場合には、リテラルの数値(または望むなら文字列)を追加したDTOの新しい_ユニオン型_を定義することで、後から型にバージョニングを追加することができます。_strictNullChecks_を有効にしていれば、バージョン0をundefinedとマークするだけで、そのバージョンの型が使われているかどうかのチェックを自動的に行えます。

このように定義したDTOは、次のように利用します。

Redux

ユニオン判別を活用しているポピュラーなライブラリはreduxです。

ここに、TypeScript型アノテーションを追加した gist of redux があります:

これをTypeScriptで使うことにより、型安全性とリファクタ容易性、そしてコードの自己文書化を図ることができます。

最終更新

役に立ちましたか?