メインコンテンツまでスキップ

判別可能なユニオン (discriminated union)

次のようなタイプエイリアスのSuccessResponse, ErrorResponseを考え、そのユニオン型としてResponseを考えます。

typescript
type SuccessResponse = {
success: true;
response: Data;
};
type ErrorResponse = {
success: false;
error: Error;
};
type Response = SuccessResponse | ErrorResponse;
typescript
type SuccessResponse = {
success: true;
response: Data;
};
type ErrorResponse = {
success: false;
error: Error;
};
type Response = SuccessResponse | ErrorResponse;

ユニオン型のResponseはふたつのタイプエイリアスが持つsuccessを共通のプロパティとして持ちますが片方はtrueでもう片方はfalseです。ここはboolean型とせずあえてリテラル型にしています

そしてこのRequestを返す関数req()があり、それを呼び戻り値を定数resで受けたとすると次のようなことができます。

typescript
const res: Response = req();
if (res.success) {
// res.response ...
} else {
// res.error ...
}
typescript
const res: Response = req();
if (res.success) {
// res.response ...
} else {
// res.error ...
}

ifの条件がtrueになる、つまりres.successtrueになるとそのブロックではres.responseを呼び出せます。一方elseのブロックではres.errorを呼び出せます。これはres.successtrueの場合はSuccessResponseであることが確定しfalseの場合はErrorResponseであることが確定するからです。

値があるかもしれないしないかもしれないことを意味するモナドのOptionalをユニオン型を使って表現するとこのようになるでしょう。

typescript
type Some<T> = {
present: true;
value: T;
};
type None = {
present: false;
};
type Optional<T> = Some<T> | None;
typescript
type Some<T> = {
present: true;
value: T;
};
type None = {
present: false;
};
type Optional<T> = Some<T> | None;

リテラル型を使えばtrue, falseに限らず他の型でも可能です。

typescript
type English = {
iso639: "en";
thanks: "thank you very much";
};
type French = {
iso639: "fr";
merci: "merci beaucoup";
};
type German = {
iso639: "de";
danke: "danke schön";
};
type Langauge = English | French | German;
const lang: Langauge = select();
switch (lang.iso639) {
case "en":
return lang.thanks;
case "fr":
return lang.merci;
case "de":
return lang.danke;
}
typescript
type English = {
iso639: "en";
thanks: "thank you very much";
};
type French = {
iso639: "fr";
merci: "merci beaucoup";
};
type German = {
iso639: "de";
danke: "danke schön";
};
type Langauge = English | French | German;
const lang: Langauge = select();
switch (lang.iso639) {
case "en":
return lang.thanks;
case "fr":
return lang.merci;
case "de":
return lang.danke;
}

上記例ではlang.iso639がそれに該当します。

リテラル型でなくても他の型どうしであればTypeScriptはこの判別を自動的にしてくれます。

typescript
type Measurement = {
b: number;
w: number;
h: number;
};
type TopSecret = {
b: "secret";
w: "secret";
h: "secret";
};
type ThreeSize = Measurement | TopSecret;
const size: ThreeSize = measure();
if (size.b === "secret") {
console.log(size.w);
// -> 'secret'
console.log(size.h);
// -> 'secret'
}
typescript
type Measurement = {
b: number;
w: number;
h: number;
};
type TopSecret = {
b: "secret";
w: "secret";
h: "secret";
};
type ThreeSize = Measurement | TopSecret;
const size: ThreeSize = measure();
if (size.b === "secret") {
console.log(size.w);
// -> 'secret'
console.log(size.h);
// -> 'secret'
}

スリーサイズを公表したくない人は'secret'という文字をどこかひとつでもに入れておけばTopSecret型であると判別され、対応するifブロックではすべてのサイズは'secret'になります。