TypeScript で型システムを強化するスマートな方法: 相互排他的型で実現する堅牢なコード
TypeScript における相互排他的型
この機能は、型システムの厳格性を高め、コードの明確性と信頼性を向上させるために役立ちます。
相互排他的型の構文
相互排他的型の構文は以下の通りです。
type MyType = Type1 | Type2 | ... | TypeN;
ここで、Type1
, Type2
, ..., TypeN
は、相互排他的型を構成する型候補を表します。
以下に、相互排他的型の具体的な例を示します。
type Shape = Circle | Rectangle | Triangle;
function getArea(shape: Shape): number {
switch (shape) {
case "circle":
return Math.PI * shape.radius * shape.radius;
case "rectangle":
return shape.width * shape.height;
case "triangle":
return (shape.base * shape.height) / 2;
default:
throw new Error("Invalid shape type");
}
}
この例では、Shape
型は、Circle
, Rectangle
, Triangle
のいずれかの型であることを意味します。getArea
関数は、shape
引数を受け取り、その形状に基づいて面積を計算します。shape
が Circle
, Rectangle
, Triangle
以外の型である場合、エラーがスローされます。
- コンパイラによるエラーチェックを強化する: コンパイラは、相互排他的型の違反を検出して、開発者に警告することができます。
- コードの明確性と信頼性を向上させる: コードの意図を明確に伝え、実行時のエラーを減らします。
- 型システムの厳格性を高める: ある変数に対して、矛盾する型を割り当てることを防ぎます。
- 複雑な型定義になる場合がある: 多くの型候補を扱う場合、型定義が複雑になり、可読性が低下する可能性があります。
- すべての型候補を網羅する必要がある: すべての潜在的な型候補を網羅しないと、コンパイルエラーが発生する可能性があります。
相互排他的型は、TypeScript における強力な型システム機能です。この機能を活用することで、コードの型安全性と信頼性を向上させることができます。
type FileType = "image" | "video" | "document";
function getFileSize(file: FileType, size: number): string {
switch (file) {
case "image":
return `${size}KB (Image file)`;
case "video":
return `${size}MB (Video file)`;
case "document":
return `${size}KB (Document file)`;
default:
throw new Error("Invalid file type");
}
}
const imageFile: FileType = "image";
const imageSize = 1024;
console.log(getFileSize(imageFile, imageSize)); // Output: 1024KB (Image file)
const videoFile: FileType = "video";
const videoSize = 51200;
console.log(getFileSize(videoFile, videoSize)); // Output: 50MB (Video file)
const documentFile: FileType = "document";
const documentSize = 4096;
console.log(getFileSize(documentFile, documentSize)); // Output: 4096KB (Document file)
この例では、FileType
型は、image
, video
, document
のいずれかのファイルの種類を表します。getFileSize
関数は、ファイルの種類とサイズを受け取り、ファイルサイズの情報を返します。
例 2: 値の範囲を表現する型
type TemperatureRange = "cold" | [number, number]; // 温度範囲を表現
function adjustTemperature(temperature: TemperatureRange, adjustment: number): TemperatureRange {
if (typeof temperature === "string") {
return temperature; // 温度範囲が文字列の場合はそのまま返す
} else {
const [lower, upper] = temperature;
const newLower = Math.max(lower + adjustment, 0);
const newUpper = Math.min(upper + adjustment, 100);
return [newLower, newUpper]; // 温度範囲を調整して返す
}
}
const coldTemperature: TemperatureRange = "cold";
console.log(adjustTemperature(coldTemperature, 10)); // Output: cold (温度範囲が文字列の場合はそのまま返す)
const moderateTemperature: TemperatureRange = [20, 25];
console.log(adjustTemperature(moderateTemperature, 5)); // Output: [25, 30] (温度範囲を調整して返す)
この例では、TemperatureRange
型は、cold
という文字列または、下限と上限の温度値の配列のいずれかを表します。adjustTemperature
関数は、温度範囲と調整値を受け取り、調整された温度範囲を返します。
例 3: ユーザーの状態を表現する型
type UserState = "active" | "inactive" | "suspended";
function handleUserState(state: UserState): void {
switch (state) {
case "active":
console.log("User is active");
break;
case "inactive":
console.log("User is inactive");
break;
case "suspended":
console.log("User is suspended");
break;
default:
throw new Error("Invalid user state");
}
}
const activeUser: UserState = "active";
handleUserState(activeUser); // Output: User is active
const inactiveUser: UserState = "inactive";
handleUserState(inactiveUser); // Output: User is inactive
const suspendedUser: UserState = "suspended";
handleUserState(suspendedUser); // Output: User is suspended
この例では、UserState
型は、active
, inactive
, suspended
のいずれかのユーザーの状態を表します。handleUserState
関数は、ユーザーの状態を受け取り、その状態に応じて処理を行います。
discriminated unions と型ガード
discriminated unions
と type guards
を組み合わせることで、相互排他的型の機能をある程度再現することができます。
type FileType = {
type: "image" | "video" | "document";
size: number;
};
function getFileSize(file: FileType): string {
if (file.type === "image") {
return `${file.size}KB (Image file)`;
} else if (file.type === "video") {
return `${file.size}MB (Video file)`;
} else if (file.type === "document") {
return `${file.size}KB (Document file)`;
} else {
throw new Error("Invalid file type");
}
}
const imageFile: FileType = { type: "image", size: 1024 };
console.log(getFileSize(imageFile)); // Output: 1024KB (Image file)
const videoFile: FileType = { type: "video", size: 51200 };
console.log(getFileSize(videoFile)); // Output: 50MB (Video file)
const documentFile: FileType = { type: "document", size: 4096 };
console.log(getFileSize(documentFile)); // Output: 4096KB (Document file)
この例では、FileType
型は、type
プロパティと size
プロパティを持つオブジェクトとして定義されています。getFileSize
関数は、file
オブジェクトを受け取り、その type
プロパティに基づいてファイルサイズの情報を返します。
この方法は、相互排他的型よりも柔軟性がありますが、type guards
を適切に使用しないと、型エラーが発生する可能性があります。
カスタム型ガード
より複雑なロジックが必要な場合は、カスタムの型ガードを作成することができます。
例:有効な年齢範囲を表現する型
type AgeRange = {
type: "adult" | "child";
minAge: number;
maxAge: number;
};
function isValidAge(age: number, ageRange: AgeRange): boolean {
if (ageRange.type === "adult") {
return age >= ageRange.minAge && age <= ageRange.maxAge;
} else if (ageRange.type === "child") {
return age >= 0 && age < ageRange.maxAge;
} else {
return false;
}
}
const adultAgeRange: AgeRange = { type: "adult", minAge: 18, maxAge: 100 };
console.log(isValidAge(25, adultAgeRange)); // Output: true
const childAgeRange: AgeRange = { type: "child", maxAge: 12 };
console.log(isValidAge(10, childAgeRange)); // Output: true
console.log(isValidAge(150, adultAgeRange)); // Output: false
console.log(isValidAge(-1, childAgeRange)); // Output: false
この例では、AgeRange
型は、type
プロパティ、minAge
プロパティ、maxAge
プロパティを持つオブジェクトとして定義されています。isValidAge
関数は、年齢と年齢範囲を受け取り、その年齢がその範囲内に収まるかどうかを判定します。
この方法は、複雑なロジックをカプセル化できるという利点がありますが、コードが煩雑になる可能性があります。
相互排他的型は、TypeScript における強力な型システム機能ですが、状況によっては、他の方法で同様の効果を実現することもできます。
typescript