TypeScript リテラル型
1. TypeScript における Literal Types の理解
TypeScript の Literal Types(リテラル型) は、変数に代入できる「正確な値」を直接指定できる機能です。これは string や number といった抽象的な型よりもはるかに高い精度を提供します。
これらは、精密で型安全なアプリケーションを構築するための構成要素となります。
1.1 主要なコンセプト
- String Literals(文字列リテラル):
"success" | "error"のような特定の文字列値 - Numeric Literals(数値リテラル):
1 | 2 | 3のような特定の数値 - Boolean Literals(真偽値リテラル):
trueまたはfalse - Template Literal Types(テンプレートリテラル型): テンプレート文字列の構文を使用して構築される文字列リテラル型
1.2 一般的なユースケース
- 許可される値のセットを厳密に定義する
- Discriminated Unions(識別子付き共用体)の作成
- 型安全なイベントハンドリング
- API レスポンスの型定義
- コンフィギュレーションオブジェクト
2. String Literal Types
文字列リテラル型は、特定の文字列値を表します。
コード例
// 文字列リテラル型の変数
let direction: "north" | "south" | "east" | "west";
// 有効な代入
direction = "north";
direction = "south";
// 無効な代入はエラーを引き起こします
// direction = "northeast"; // エラー: 型 '"northeast"' を型 '"north" | "south" | "east" | "west"' に割り当てることはできません
// direction = "up"; // エラー: 型 '"up"' を型 '"north" | "south" | "east" | "west"' に割り当てることはできません
// 関数で文字列リテラル型を使用する
function move(direction: "north" | "south" | "east" | "west") {
console.log(`${direction} に移動中`);
}
move("east"); // 有効
// move("up"); // エラー: 型 '"up"' の引数は型 '"north" | "south" | "east" | "west"' のパラメータに割り当てることはできません...3. Numeric Literal Types
文字列リテラルと同様に、数値リテラル型は特定の数値を表します。
コード例
// 数値リテラル型の変数
let diceRoll: 1 | 2 | 3 | 4 | 5 | 6;
// 有効な代入
diceRoll = 1;
diceRoll = 6;
// 無効な代入はエラーを引き起こします
// diceRoll = 0; // エラー: 型 '0' は型 '1 | 2 | 3 | 4 | 5 | 6' に割り当てることはできません
// diceRoll = 7; // エラー: 型 '7' は型 '1 | 2 | 3 | 4 | 5 | 6' に割り当てることはできません
// diceRoll = 2.5; // エラー: 型 '2.5' は型 '1 | 2 | 3 | 4 | 5 | 6' に割り当てることはできません
// 関数で数値リテラル型を使用する
function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
return (Math.floor(Math.random() * 6) + 1) as 1 | 2 | 3 | 4 | 5 | 6;
}
const result = rollDice();
console.log(`出目: ${result}`);4. Boolean Literal Types
真偽値リテラル型は、真偽値が2つしかないため一般的にはあまり使われませんが、特定のシナリオでは有用です。
コード例
// リテラル値 'true' のみを保持できる型
type YesOnly = true;
// 常に true を返さなければならない関数
function alwaysSucceed(): true {
// 常にリテラル値 'true' を返す
return true;
}
// 他の型と組み合わせた真偽値リテラル
type SuccessFlag = true | "success" | 1;
type FailureFlag = false | "failure" | 0;
function processResult(result: SuccessFlag | FailureFlag) {
if (result === true || result === "success" || result === 1) {
console.log("操作に成功しました");
} else {
console.log("操作に失敗しました");
}
}
processResult(true); // "操作に成功しました"
processResult("success"); // "操作に成功しました"
processResult(1); // "操作に成功しました"
processResult(false); // "操作に失敗しました"5. Literal Types with Objects
リテラル型をオブジェクト型と組み合わせることで、非常に具体的な形状(Shape)を作成できます。
コード例
// リテラルのプロパティ値を持つオブジェクト
type HTTPSuccess = {
status: 200 | 201 | 204;
statusText: "OK" | "Created" | "No Content";
data: any;
};
type HTTPError = {
status: 400 | 401 | 403 | 404 | 500;
statusText: "Bad Request" | "Unauthorized" | "Forbidden" | "Not Found" | "Internal Server Error";
error: string;
};
type HTTPResponse = HTTPSuccess | HTTPError;
function handleResponse(response: HTTPResponse) {
if (response.status >= 200 && response.status < 300) {
console.log(`成功: ${response.statusText}`);
console.log(response.data);
} else {
console.log(`エラー ${response.status}: ${response.statusText}`);
console.log(`メッセージ: ${response.error}`);
}
}
// 使用例
const successResponse: HTTPSuccess = {
status: 200,
statusText: "OK",
data: { username: "john_doe", email: "[email protected]" }
};
const errorResponse: HTTPError = {
status: 404,
statusText: "Not Found",
error: "データベースにユーザーが見つかりません"
};
handleResponse(successResponse);
handleResponse(errorResponse);6. Template Literal Types
TypeScript 4.1 以降で導入された Template Literal Types は、テンプレート文字列の構文を使用して既存のリテラル型を組み合わせ、新しい文字列リテラル型を作成できます。
コード例
// 基本的なテンプレートリテラル
type Direction = "north" | "south" | "east" | "west";
type Distance = "1km" | "5km" | "10km";
// これらを組み合わせてテンプレートリテラルを使用する
type DirectionAndDistance = `${Direction}-${Distance}`;
// "north-1km" | "north-5km" | "north-10km" | "south-1km" | ...
let route: DirectionAndDistance;
route = "north-5km"; // 有効
route = "west-10km"; // 有効
// route = "north-2km"; // エラー
// route = "5km-north"; // エラー
// 高度な文字列操作
type EventType = "click" | "hover" | "scroll";
type EventTarget = "button" | "link" | "div";
type EventName = `on${Capitalize<EventType>}${Capitalize<EventTarget>}`;
// "onClickButton" | "onClickLink" | "onClickDiv" | ...
// 動的なプロパティアクセス
type User = {
id: number;
name: string;
email: string;
createdAt: Date;
};
type GetterName<T> = `get${Capitalize<string & keyof T>}`;
type UserGetters = {
[K in keyof User as GetterName<User>]: () => User[K];
};
// { getId: () => number; getName: () => string; ... }
// 文字列パターンマッチング
type ExtractRouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<Rest>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = ExtractRouteParams<"/users/:userId/posts/:postId">; // "userId" | "postId"
// CSS の単位と値
type CssUnit = 'px' | 'em' | 'rem' | '%' | 'vh' | 'vw';
type CssValue = `${number}${CssUnit}`;
let width: CssValue = '100px'; // 有効
let height: CssValue = '50%'; // 有効
// let margin: CssValue = '10'; // エラー
// let padding: CssValue = '2ex'; // エラー
// API バージョニング
type ApiVersion = 'v1' | 'v2' | 'v3';
type Endpoint = 'users' | 'products' | 'orders';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiUrl = `https://api.example.com/${ApiVersion}/${Endpoint}`;
// 複雑な例: 動的な SQL クエリビルダー
type Table = 'users' | 'products' | 'orders';
type Column<T extends Table> =
T extends 'users' ? 'id' | 'name' | 'email' | 'created_at' :
T extends 'products' ? 'id' | 'name' | 'price' | 'in_stock' :
T extends 'orders' ? 'id' | 'user_id' | 'total' | 'status' : never;
type WhereCondition<T extends Table> = {
[K in Column<T>]?: {
equals?: any;
notEquals?: any;
in?: any[];
};
};
function query<T extends Table>(
table: T,
where?: WhereCondition<T>
): `SELECT * FROM ${T}${string}` {
// 実装はクエリを構築する
return `SELECT * FROM ${table}` as const;
}
// 使用例
const userQuery = query('users', {
name: { equals: 'John' },
created_at: { in: ['2023-01-01', '2023-12-31'] }
});
// 型: "SELECT * FROM users WHERE ..."7. ベストプラクティス
リテラル型を効果的に使うための推奨事項(Do's and Don'ts)です。
7.1 推奨事項 (Do)
- 固定された値のセット(Enum、コンフィギュレーションオプションなど)にはリテラル型を使用する
- 型安全性を高めるために共用体型(Union Types)と組み合わせる
- 文字列パターンマッチングにはテンプレートリテラル型を使用する
- 可能な限り型推論を活用する
- リテラル型の意味をドキュメント化する
7.2 非推奨事項 (Don't)
- より一般的な型が適切な場合にリテラル型を過用しない
- パフォーマンスを低下させるほど極端に大きな共用体型を作成しない
- Enum がより適切な場面で文字列リテラルを使用しすぎない(ケースバイケース)
8. パフォーマンスに関する考慮事項
8.1 型チェックのパフォーマンス
- 巨大な共用体型は型チェックを遅くする可能性があります
- 複雑なテンプレートリテラル型はコンパイル時間を増大させる可能性があります
- 複雑なリテラル型には型エイリアス(Type Aliases)の使用を検討してください
- TypeScript の再帰深度の制限に注意してください