ユニオン型は | で選択肢を並べます。string | number 型の値は、文字列か数値のどちらかです。ユニオン型は、正当に複数の形を取りうる値をTypeScriptが表す方法で、コンパイラーは、ある形に特有のものを使う前に、どの形なのかをチェックさせます。
typeof のチェックの各ブランチの中では、id は1つの型です。このステップを絞り込みといい、ユニオン型を実用的にしているのはこれです。
使えるのは共通のメンバーだけ
絞り込む前は、ユニオン型のすべてのメンバーが対応しているものしか使えません。toString() は文字列にも数値にもあるので使えますが、toUpperCase() は文字列にしかありません:
index.ts(3,16): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
Property 'toUpperCase' does not exist on type 'number'.
2行目は、そのプロパティを持たないメンバーを名指ししています。逆方向にも同じルールが当てはまります。string | number の値は、数値かもしれないので、string 型の引数には渡せません(TS2345)。ユニオン型はより多くの値を受け付ける代わりに、チェックするまではできることが少なくなります。
ユニオン型の絞り込み
絞り込みには普通のJavaScriptのチェックを使います。TypeScriptは制御フローをたどり、除外されたメンバーを取り除いていくので、最後のチェックのあとには1つのメンバーだけが残ります:
| チェック | 絞り込む先 | 向いている対象 |
|---|---|---|
typeof x === "string" | そのプリミティブ | string、number、boolean、bigint、symbol、undefined、function |
x === null、x === "a" | 比較した値 | null、undefined、リテラルのメンバー |
Array.isArray(x) | 配列のメンバー | 配列 |
x instanceof Date | そのクラス | クラスのインスタンス |
"meow" in x | そのプロパティを持つメンバー | オブジェクト型 |
x.kind === "circle" | そのタグを持つメンバー | 判別可能なユニオン型 |
isCat(x)(x is Cat を返す) | 関数が示すもの | 何でも、独自のロジック |
絞り込みの方法の一覧は、型の絞り込みのページにあります。
リテラル型のユニオン
リテラルの値のユニオン型は、許可する値の閉じた集合です。実際のコードでいちばんよく使われるユニオン型です:
type Status = "idle" | "loading" | "success" | "error";
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
type Toggle = "on" | "off" | boolean; // boolean is itself true | false
let current: Status = "idle";
current = "loading"; // fine
current = "finished"; // error TS2322: Type '"finished"' is not assignable to type 'Status'.
リテラルと比較すると絞り込まれます。if (current === "error") のあと、else のブランチでは current がほかの3つのどれかだとわかります。多くのコードベースでは、リテラルのユニオン型が enum の代わりになっています。as const と、配列からこうしたユニオン型を作る方法は リテラル型 を参照してください。
オブジェクト型のユニオン
メンバーがオブジェクト型の場合、すべてに共通するプロパティは直接使えます。それ以外は、in でプロパティがあるかをチェックします:
複数のオブジェクトの形のユニオン型なら、kind: "cat" / kind: "fish" のような共通のリテラルのプロパティを使うパターンのほうがすっきりします。その1つのプロパティをチェックすればオブジェクト全体が絞り込まれ、それに対する switch は網羅性をチェックできます。このパターンが判別可能なユニオン型です。
配列とユニオン型
かっこの位置で意味がまったく変わります:
| 型 | 意味 | 値の例 |
|---|---|---|
(string | number)[] | 各要素が文字列か数値である配列 | [1, "two", 3] |
string[] | number[] | 文字列だけの配列、または数値だけの配列 | ["a", "b"] |
string | number[] | 文字列、または数値の配列(| は [] より結合が弱い) | "text" |
(string | number)[] を反復すると、各要素はユニオン型なので、上の reduce のコールバックのように絞り込みが必要です。map や filter のようなメソッドは string[] | number[] にも使え、コールバックは string | number を受け取ります。
null と undefined を含むユニオン型
いちばんよく使われるユニオン型は「値か、何もないか」です: string | null、User | undefined。Array.prototype.find と Map.prototype.get が返すのはこれで、省略可能なプロパティ name?: string を読むと string | undefined になります。?.、??、null チェックによる扱い方には専用のページがあります: null と undefined。
既存のユニオン型から型のレベルでメンバーを取り除くには、組み込みのユーティリティを使います。Exclude<"a" | "b" | "c", "a"> は "b" | "c"、NonNullable<string | null> は string です。
よくある質問
TypeScriptのユニオン型とは何ですか?
| でつないだ複数の選択肢からなる型です。string | number 型の値は、文字列か数値のどちらかです。typeof value === "string" のようなチェックで値を1つのメンバーに絞り込むまで、コンパイラーはすべてのメンバーに共通するものしか使わせません。
ユニオン型でプロパティが存在しないと言われるのはなぜですか?
ユニオン型のメンバーの少なくとも1つがそのプロパティを持っていないからです。たとえばエラー TS2339 Property 'toUpperCase' does not exist on type 'string | number' は、値が toUpperCase を持たない数値かもしれないという意味です。先に絞り込み(typeof、in、Array.isArray、instanceof、または判別プロパティのチェック)、それからそのメンバー特有のプロパティを使いましょう。
複数の型を持つ配列を宣言するには?
ユニオン型をかっこで囲みます: (string | number)[] または Array<string | number> で、各要素がどちらの型でもかまいません。string[] | number[] は違い、配列全体がすべて文字列か、すべて数値です。かっこがないと、string | number[] は文字列か数値の配列という意味になります。
ユニオン型と交差型の違いは何ですか?
ユニオン型 A | B は型のどれか1つである値なので、共通するものしか使えません。交差型 A & B は同時に両方である値なので、両方のメンバーをすべて持ちます。オブジェクト型では、A | B はより多くの値を受け付け、A & B はより多くのプロパティを要求します。
ユニオン型の値がどの型かをチェックするには?
TypeScriptが理解できる実行時のチェックを使います。プリミティブには typeof x === "string"、配列には Array.isArray(x)、クラスには x instanceof Date、オブジェクトの形には "prop" in x、メンバーが共通のリテラルのタグを持つなら x.kind === "circle" です。独自のロジックには、x is T を返す型ガード関数を書きます。