TypeScript のユニオン型とリテラル型
ユニオン型とリテラル型は、TypeScriptがJavaScriptを凌駕する第一歩です。これらを用いることで、「この値はAかBのいずれかである」ことや、「この値はこれらの選択肢のうちの1つに限られる」ことを表現できるようになります。
1. ユニオン型
ユニオン型は | を使用して複数の型を組み合わせ、その値が「これらの型のいずれかである」ことを示します:
(1) 基本的な構文
TYPESCRIPT
let id: number | string;
id = 42; // ✅ number Legal
id = "ABC"; // ✅ string Legal
id = true; // ❌ boolean Not in a composite type
(2) 一般的な用途:関数の引数
ユニオン型の最も一般的な用途は、関数がさまざまな型の引数を受け取れるようにすることです:
TYPESCRIPT
function printId(id: number | string) {
console.log("YourID: :" + id);
}
printId(42); // ✅
printId("ABC"); // ✅
printId(true); // ❌
(3) ユニオン型の制限
union型の変数は、すべての型に共通するメンバにのみアクセスできます:
TYPESCRIPT
function process(value: number | string) {
// value.toString() // ✅ number and string All of them toString
// value.toUpperCase() // ❌ number None toUpperCase
// value.toFixed(2) // ❌ string None toFixed
}
📌 理由: TypeScript は、
value がユニオンのどの要素であっても、コードの安全性が保たれるようにする必要があります。すべての要素に共通するプロパティやメソッドのみを直接呼び出すことができます。特定の型の要素にアクセスするには、まず「型の絞り込み」を行う必要があります(第3節を参照)。
2. リテラル型
リテラル型は、変数の値を特定のリテラルに制限します。つまり、「任意の文字列」ではなく、「これらの特定の文字列のみ」となります。
(1) 3種類のリテラル
| リテラル型 | 構文 | 例 |
|---|---|---|
| 文字列リテラル | "value1" | "value2" |
"north" | "south" | "east" | "west" |
| 数値リテラル | 1 | 2 | 3 |
0 | 1 | 2 |
| ブールリテラル | true | false |
boolean と実際に等しい(値は2つだけ) |
(2) 文字列リテラル型
TYPESCRIPT
let direction: "north" | "south" | "east" | "west";
direction = "north"; // ✅
direction = "up"; // ❌ "up" Not in a composite type
(3) 数値リテラル型
TYPESCRIPT
type HttpStatusCode = 200 | 301 | 404 | 500;
let code: HttpStatusCode = 200; // ✅
let code2: HttpStatusCode = 201; // ❌ 201 Not in a composite type
(4) ブールリテラル型
TYPESCRIPT
type YesNo = true | false; // equivalent to boolean
type StrictTrue = true; // It can only be true
💡 ヒント: ブールリテラル型はそれ自体ではあまり意味を持ちませんが(
true | false は単なるブール値です)、ユニオン型で他の型と組み合わせると便利です。例えば、string | true は「文字列または true」を表します。
▶ 例:リテラル型を使用したロールの権限の定義
TYPESCRIPT
type Role = "admin" | "editor" | "viewer";
function checkPermission(role: Role) {
if (role === "admin") {
console.log("Administrator:Full Access");
} else if (role === "editor") {
console.log("Editor:Editable Content");
} else {
console.log("Viewers:Read-only access");
}
}
checkPermission("admin"); // ✅
checkPermission("editor"); // ✅
checkPermission("guest"); // ❌ Compilation Error——"guest" Not legal Role
出力:
TEXT
Administrator:Full Access
Editor:Editable Content
3. 型絞り込み(Type Narrowing)
union型の変数は、特定の型のメンバに直接アクセスすることはできませんが、「型絞り込み」によってスコープを絞り込むことができます。
(1) typeofによる型絞り込み
TYPESCRIPT
function process(value: number | string) {
if (typeof value === "string") {
// In this branch,TypeScript knows value is string
console.log(value.toUpperCase()); // ✅ string Methods
} else {
// In this branch,TypeScript knows value is number
console.log(value.toFixed(2)); // ✅ number Methods
}
}
(2) 平等の範囲の狭窄
TYPESCRIPT
function compare(a: string | number, b: string | boolean) {
if (a === b) {
// a and b equal,Their intersection type is string
console.log(a.toUpperCase()); // ✅ Here a It must be string
}
}
(3) in 演算子の範囲の絞り込み
TYPESCRIPT
function process(input: { name: string } | { age: number }) {
if ("name" in input) {
console.log(input.name); // ✅ Has name property,Inferred as { name: string }
} else {
console.log(input.age); // ✅ None name,Inferred as { age: number }
}
}
(4) instanceof による型絞り込み
TYPESCRIPT
function processDate(value: Date | string) {
if (value instanceof Date) {
console.log(value.toISOString()); // ✅ Date Methods
} else {
console.log(value.toUpperCase()); // ✅ string Methods
}
}
▶ 例:型絞り込みの完全な実例
TYPESCRIPT
function formatValue(value: number | string | boolean): string {
if (typeof value === "number") {
return "Numbers:" + value.toFixed(2);
} else if (typeof value === "string") {
return "String:" + value.toUpperCase();
} else {
return "Boolean:" + value;
}
}
console.log(formatValue(3.14159));
console.log(formatValue("hello"));
console.log(formatValue(true));
出力:
TEXT
Numbers:3.14
String:HELLO
Boolean:true
4. ユニオン型およびリテラル型のための古典的なパターン
ユニオン型とリテラル型を組み合わせることで、非常に強力な型制約を実現できます。以下に、3つの典型的なパターンを挙げます。
(1) パターン 1:区別された和集合
同じプロパティに対して異なるリテラル値を使用することで、ユニオンのメンバーを区別します:
TYPESCRIPT
interface Circle {
kind: "circle"; // Identifiable Attributes
radius: number;
}
interface Rectangle {
kind: "rectangle"; // Identifiable Attributes
width: number;
height: number;
}
type Shape = Circle | Rectangle;
function getArea(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2; // ✅ Accessible radius
} else {
return shape.width * shape.height; // ✅ Accessible width、height
}
}
📌 ポイント: 各インターフェースには
kind プロパティがありますが、その値はそれぞれ異なるリテラル型です。TypeScript は kind の値を使用して型を絞り込み、そのインターフェース固有のプロパティに安全にアクセスします。
(2) パターン 2:オプションの値 + 未定義
TYPESCRIPT
type Result = string | undefined;
function search(query: string): Result {
if (query.length === 0) return undefined;
return "Found:" + query;
}
let result = search("TypeScript");
if (result !== undefined) {
console.log(result.toUpperCase()); // ✅ narrowed to string
}
(3) パターン 3:ステートマシン
TYPESCRIPT
type RequestStatus = "idle" | "loading" | "success" | "error";
interface RequestState {
status: RequestStatus;
data?: string;
error?: string;
}
function renderState(state: RequestState): string {
switch (state.status) {
case "idle":
return "Waiting......";
case "loading":
return "Loading......";
case "success":
return "Success:" + state.data; // ✅ success in that state data Existence
case "error":
return "Error:" + state.error; // ✅ error in that state error Existence
}
}
5. never 型と網羅的なチェック
TypeScript が「あり得ないシナリオ」に絞り込まれた場合、型は never になります:
TYPESCRIPT
type Shape = "circle" | "square";
function getIcon(shape: Shape): string {
switch (shape) {
case "circle": return "⭕";
case "square": return "⬜";
default:
// If all case It's all taken care of.,shape Here, the type is never
const _exhaustiveCheck: never = shape; // ✅ If omitted case,An error will occur here
return _exhaustiveCheck;
}
}
💡 ヒント:
never の網羅的なチェックパターン――switch ステートメントの default 分岐内で、never 型の変数に値を代入する。今後、Shapeに新しいメンバーを追加した際、対応するcaseを追加し忘れた場合、TypeScriptはdefault分岐でエラーを発生させ、追加を促します。
▶ 例:網羅的チェックの実用的な効果
TYPESCRIPT
type TrafficLight = "red" | "yellow" | "green";
function getAction(light: TrafficLight): string {
switch (light) {
case "red": return "Stop";
case "yellow": return "Note";
// Intentional omission "green" 's case
default:
// If you add the next line,TypeScript It will throw an error:
// You cannot convert the type "green" Assignment to a Type "never"
const exhaustive: never = light;
return "Unknown";
}
}
❓ よくある質問
Q 和集合型と積集合型の違いは何ですか?
A 和集合型 (A | B) は「A または B」を意味します。これは和集合をとるため、値はどちらか一方を満たせば十分です。一方、積集合型 (A & B) は「A と B の両方」を意味します。これは積集合をとるため、値は両方を満たす必要があります。第20課では、積集合型について詳しく説明します。
Q リテラル型と列挙型、どちらを使うべきですか?
A ユニオンリテラル型を推奨します。列挙型よりも軽量(コンパイル時に余分なコードが生成されない)であり、JavaScriptのネイティブ文字列や数値と完全に互換性があります。列挙型は、逆マッピング(値 → 名前)が必要な場合や、実行時にすべてのメンバーを反復処理する必要がある場合に適しています。第12課では、この2つを比較します。
Q
typeof はすべての型を検出できますか?A いいえ。
typeof が識別できるのは、「number」、「string」、「boolean」、「symbol」、「bigint」、「object」、「function」、および「undefined」の型のみです。null(typeof nullは「object」を返します)などの参照型、配列、およびDateについては、instanceofやその他の方法を使用して型を絞り込む必要があります。詳細な説明については、第17課を参照してください。Q
never 型はどのような場合に使用されますか?A
never 型は主に次の 2 つの場面で現れます:(1) 型が不可能な分岐に絞り込まれたときに自動的に現れる場合、および (2) 関数が決して値を返さない場合(例外がスローされたり、無限ループが発生したりする場合など)。初心者は、「網羅的チェック」というパターンさえ理解しておけば十分です。これは、switchのdefault節でneverを使用し、欠落しているcaseがないかを確認するものです。📖 まとめ
- 結合型は
|を使用して複数の型を組み合わせ、その値がそれらのいずれかになることを示します - リテラル型は、値を特定のリテラル(文字列、数値、ブール値)に制限し、ユニオン型と組み合わせることで列挙型として機能します。
- ユニオン型の変数は、すべてのメンバーに共通するプロパティにしかアクセスできません。特定のメンバーにアクセスするには、
typeof、in、またはinstanceofを使用して「絞り込み」を行う必要があります。 - 識別付き和集合(discriminated union)とは、和集合とリテラルを組み合わせた古典的なパターンであり、共通する属性の値に基づいて型を区別するものです。
never型は、switchステートメントがすべての可能性を網羅していることを確認するための網羅的チェックに使用されます。
📝 練習問題
- 基本問題(難易度 ⭐):ユニオン型
Status = "active" | "inactive" | "banned"を定義し、それに対応する中国語の説明を返す関数getStatusText(status: Status)を作成してください。 - 上級問題(難易度 ⭐⭐):区別可能な和集合型
Result = SuccessResult | ErrorResultを定義せよ。ここで、SuccessResultはsuccess: trueとdata: stringからなり、ErrorResultはsuccess: falseとerror: stringからなるものとする。Resultを処理し、success, safely accesses eitherdataorerrorの値に基づいて処理を行う関数を記述せよ。 - チャレンジ問題(難易度 ⭐⭐⭐):switch文で7日間すべてを処理するために、網羅的チェックを用いた関数
describeDay(day: "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun")を作成してください。その後、意図的に1日を省略し、「決して起こらない」はずの網羅的チェックによって生成されるエラーメッセージを確認してください。



