これまでの章では、string や number、あるいは特定のオブジェクトの形といった「単一の型」を扱ってきました。しかし、現実のアプリケーション開発(特にJavaScriptの世界)では、「IDは数値かもしれないし、文字列かもしれない」「成功時はデータを返すが、失敗時はエラーメッセージを返す」といった柔軟なデータ構造が頻繁に登場します。
この章では、既存の型をパズルのように組み合わせて、より複雑で柔軟な状況を表現する方法を学びます。
Union型(共用体型)は、**「A または B」**という状態を表現します。パイプ記号 | を使用して記述します。
JavaScriptでは変数の型が動的であるため、1つの変数に異なる型の値が入ることがよくありますが、TypeScriptではUnion型を使ってこれを安全に定義できます。
// idは数値、または文字列を許容する
let id: number | string;
id = 101; // OK
id = "user-a"; // OK
// id = true; // Error: Type 'boolean' is not assignable to type 'string | number'.
function printId(id: number | string) {
console.log(`Your ID is: ${id}`);
}
printId(123);
printId("ABC");npx tsc union-basic.ts && node union-basic.jsYour ID is: 123 Your ID is: ABC
注意点: Union型を使用している変数は、その時点では「どの型か確定していない」ため、すべての候補に共通するプロパティやメソッドしか操作できません。特定の型として扱いたい場合は、後述する「型ガード」を使用します。
string や number は「あらゆる文字列」や「あらゆる数値」を受け入れますが、**「特定の値だけ」**を許可したい場合があります。これをLiteral型(リテラル型)と呼びます。
通常、Literal型は単独で使うよりも、Union型と組み合わせて**「決まった選択肢のいずれか」**を表現するのによく使われます(Enumの代わりとしてもよく利用されます)。
// 文字列リテラル型とUnion型の組み合わせ
type TrafficLight = 'red' | 'yellow' | 'green';
let currentLight: TrafficLight = 'red';
// currentLight = 'blue'; // Error: Type '"blue"' is not assignable to type 'TrafficLight'.
// 数値リテラルも可能
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let dice: DiceRoll = 3;
console.log(`Light: ${currentLight}, Dice: ${dice}`);npx tsc literal-types.ts && node literal-types.jsLight: red, Dice: 3
Intersection型(交差型)は、**「A かつ B」を表します。アンパサンド & を使用します。
これは主にオブジェクトの型定義を合成(マージ)して、「複数の型のすべてのプロパティを持つ新しい型」**を作る際によく使用されます。
type Person = {
name: string;
};
type Employee = {
employeeId: number;
department: string;
};
// Person かつ Employee の特徴を持つ型
type CompanyMember = Person & Employee;
const member: CompanyMember = {
name: "Suzuki",
employeeId: 5001,
department: "Engineering"
// どれか一つでも欠けるとエラーになります
};
console.log(member);npx tsc intersection-types.ts && node intersection-types.js{ name: 'Suzuki', employeeId: 5001, department: 'Engineering' }補足: プリミティブ型同士で
string & numberのように交差させると、両方を満たす値は存在しないため、型はnever(ありえない値)になります。Intersection型は主にオブジェクト型の合成に使われます。
TypeScriptには null 型と undefined 型が存在します。
tsconfig.json の設定で strictNullChecks: true(推奨設定)になっている場合、これらは他の型(stringなど)には代入できません。
値が存在しない可能性がある場合は、Union型を使って明示的に null や undefined を許可します。
// string または null を許容する
let userName: string | null = "Tanaka";
userName = null; // OK
// オプショナルなプロパティ(?)は 「型 | undefined」 の糖衣構文に近い動きをします
type UserConfig = {
theme: string;
notification?: boolean; // boolean | undefined
};
const config: UserConfig = {
theme: "dark"
// notification は省略可能 (undefined)
};
console.log(`User: ${userName}, Theme: ${config.theme}`);npx tsc nullable.ts && node nullable.jsUser: null, Theme: dark
Union型 (string | number) の変数があるとき、プログラムの中で「今は string なのか number なのか」を区別して処理を分けたい場合があります。これを**型の絞り込み(Narrowing)**と言います。
TypeScriptのコンパイラが「このブロック内ではこの変数はこの型だ」と認識できるようにするチェック処理を型ガードと呼びます。
プリミティブ型(string, number, boolean, symbol, undefined)の判定に使います。
function formatPrice(price: number | string) {
// ここでは price は number | string
if (typeof price === 'string') {
// このブロック内では price は 'string' 型として扱われる
return parseInt(price).toLocaleString();
} else {
// このブロック内では price は 'number' 型として扱われる
return price.toLocaleString();
}
}
console.log(formatPrice(10000));
console.log(formatPrice("20000"));npx tsc type-guard-typeof.ts && node type-guard-typeof.js10,000 20,000
オブジェクトが特定のプロパティを持っているかどうかで型を絞り込みます。
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ('swim' in animal) {
// ここでは Fish 型
animal.swim();
} else {
// ここでは Bird 型
animal.fly();
}
}
const fish: Fish = { swim: () => console.log("Swimming...") };
move(fish);npx tsc type-guard-in.ts && node type-guard-in.jsSwimming...
クラスのインスタンスかどうかを判定します(第7章のクラスで詳しく扱いますが、Dateなどの組み込みオブジェクトでも有効です)。
function logDate(value: string | Date) {
if (value instanceof Date) {
console.log(value.toISOString());
} else {
console.log(value);
}
}時に、プログラマがTypeScriptコンパイラよりも型の詳細を知っている場合があります。例えば、外部APIからのレスポンスや、DOM要素の取得などです。
as キーワードを使うと、コンパイラに対して「この変数はこの型であるとして扱ってくれ」と強制できます。
// unknown型は何でも入るが、そのままでは操作できない型 let someValue: unknown = "This is a string"; // コンパイラに「これはstringだからlengthを使わせて」と伝える let strLength: number = (someValue as string).length; console.log(strLength); // 注意: 全く互換性のない型への変換はエラーになりますが、 // unknownを経由すると無理やり変換できてしまうため、乱用は避けてください。 // let wrong = (123 as string); // Error // let dangerous = (123 as unknown as string); // OKだが実行時にバグの元
npx tsc assertion.ts && node assertion.js16
注意: 型アサーションはあくまで「コンパイル時の型チェックを黙らせる」機能であり、実行時の型変換を行うわけではありません。実行時に値が想定と違う場合、クラッシュの原因になります。可能な限り、型ガードを使って安全に絞り込むことを推奨します。
|): 複数の型のうち「いずれか」を表す。
&): 複数の型を「合成」して、すべてのプロパティを持つ型を作る。strictNullChecks 環境下では、Union型を使って明示的に許容する必要がある。typeof, in, instanceof などを使って、Union型から特定の型へ絞り込む。as): 型を強制的に指定するが、安全性のために使用は慎重に行う。APIリクエストの結果を表す Result 型を定義してください。
success: true と data: string を持ちます。success: false と error: string を持ちます。handleResult 関数内で型ガードを使い、成功ならデータを、失敗ならエラーメッセージをログ出力してください。// ここに SuccessResult, FailureResult, Result 型を定義してください
// type Result = ...
function handleResult(result: Result) {
// ここに処理を実装してください
}
// テスト用
handleResult({ success: true, data: "Data loaded" });
handleResult({ success: false, error: "Network error" });npx tsc practice5_1.ts && node practice5_1.jsCircle 型と Square 型を定義し、それらのUnion型である Shape を定義してください。
Circle は kind: 'circle' と radius: number を持ちます。Square は kind: 'square' と sideLength: number を持ちます。getArea 関数で、渡された図形に応じて面積を計算して返してください(円周率は Math.PI を使用)。// ここに型を定義
function getArea(shape: Shape): number {
// ここに実装 (switch文やif文で kind プロパティによる絞り込みを行う)
return 0;
}
// テスト用
console.log(getArea({ kind: 'circle', radius: 10 }));
console.log(getArea({ kind: 'square', sideLength: 5 }));npx tsc practice5_2.ts && node practice5_2.js