
今日のテーマ:選択肢を限定したい時に使う型

先生、HTMLのセレクトボックスみたいに、決まった選択肢の中からしか値を選べないようにしたい時って、TypeScriptではどうすればいいんですか?

そんな時に使えるのがEnum型とリテラル型という2つの方法があるんだ。まずはEnum型から見てみよう。
Enum型とは?

Enumは『enumeration(列挙)』の略で、日本語では『列挙型』と呼ばれるんだ。決まった値の集合を定義できる型だよ。

enumって他のプログラミング言語でも聞いたことがあります!

そうそう。でも、TypeScriptのenumはちょっと注意が必要なんだ。まずは基本的な使い方を見てみよう。
// ゲームのキャラクタータイプを定義
enum CharacterType {
Warrior, // 0
Mage, // 1
Archer // 2
}
// 使い方
console.log(CharacterType.Warrior); // 0
console.log(CharacterType.Mage); // 1
console.log(CharacterType.Archer); // 2
// 変数に代入
let playerType: CharacterType = CharacterType.Warrior;
console.log(playerType); // 0

え!実際の値は数値なんですか?

そうなんだ。これが問題の始まりなんだよ。見てみて。
数値Enumの問題点
enum CharacterType {
Warrior, // 0
Mage, // 1
Archer // 2
}
let playerType: CharacterType = CharacterType.Warrior;
// 普通の使い方
playerType = CharacterType.Mage; // OK
console.log(playerType); // 1
// 問題:直接数値を代入してもエラーにならない!
playerType = 99; // エラーにならない!これは危険
console.log(playerType); // 99
// 存在しない値でも受け入れてしまう
playerType = -5; // これもエラーにならない

うわあ、これは困りますね!99とか-5とか、全然意味のない値が入っちゃってる!

そう。これは型安全性が保証されていないということなんだ。でも、文字列Enumを使えばこの問題は解決できるよ。
文字列Enum(改良版)
// 文字列Enumで安全に定義
enum CharacterType {
Warrior = 'warrior',
Mage = 'mage',
Archer = 'archer'
}
let playerType: CharacterType = CharacterType.Warrior;
console.log(playerType); // 'warrior'
// 型安全性が保たれる
playerType = CharacterType.Mage; // OK
// 直接文字列を代入しようとするとエラー
// playerType = 'warrior'; // エラー!
// playerType = 'ninja'; // エラー!

おお!今度はちゃんとエラーになってますね。でも、なぜ ‘warrior’ の代入もダメなんですか?

これが型安全性というものなんだ。文字列 ‘warrior’ とEnum型の CharacterType.Warrior は、値は同じでも型が違うんだよ。だから代入できないんだ。
リテラル型の登場

実は、TypeScriptにはもう一つ同じような働きをする型があるんだ。リテラル型というものだよ。

リテラル型?配列リテラルとかオブジェクトリテラルの『リテラル』ですか?

ちょっと紛らわしいんだけど、ここでは『literal(文字通りの、そのままの)』という意味なんだ。特定の値そのものを型として使うんだよ。
リテラル型の基本
// 特定の文字列だけを許可する型
let gameMode: 'easy' = 'easy'; // 'easy'という文字列のみ許可
// 他の値を代入しようとするとエラー
// gameMode = 'hard'; // エラー!
// gameMode = 'normal'; // エラー!
// 数値リテラル型の例
let maxLevel: 100 = 100; // 100という数値のみ許可
// maxLevel = 99; // エラー!

なるほど!でもこれだと一つの値しか使えないから、選択肢を作るのには向いてませんよね?

そこで登場するのがユニオン型との組み合わせなんだ!
ユニオン型と組み合わせたリテラル型
// ゲーム難易度の選択肢を定義
type GameDifficulty = 'beginner' | 'normal' | 'hard' | 'expert';
let difficulty: GameDifficulty = 'normal'; // OK
// 定義された値のみ使用可能
difficulty = 'beginner'; // OK
difficulty = 'expert'; // OK
// 未定義の値はエラー
// difficulty = 'impossible'; // エラー!
// difficulty = 'easy'; // エラー!
// 実用的な例:HTTPメソッドの定義
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
function sendRequest(method: HttpMethod, url: string) {
console.log(`${method}リクエストを${url}に送信します`);
}
sendRequest('GET', '/api/users'); // OK
sendRequest('POST', '/api/users'); // OK
// sendRequest('PATCH', '/api/users'); // エラー!

すごい!これなら決まった選択肢の中からしか選べませんね!
Enum型 vs リテラル型:どちらを使うべき?

実は、現在のTypeScriptコミュニティではリテラル型の方が人気なんだ。理由を比較してみよう。
// Enum型の場合
enum Status {
Loading = 'loading',
Success = 'success',
Error = 'error'
}
function handleStatus(status: Status) {
switch (status) {
case Status.Loading:
console.log('読み込み中...');
break;
case Status.Success:
console.log('成功!');
break;
case Status.Error:
console.log('エラーが発生しました');
break;
}
}
// リテラル型の場合
type Status = 'loading' | 'success' | 'error';
function handleStatus(status: Status) {
switch (status) {
case 'loading':
console.log('読み込み中...');
break;
case 'success':
console.log('成功!');
break;
case 'error':
console.log('エラーが発生しました');
break;
}
}
リテラル型の利点
- シンプルな記述:Enum.ValueではなくただのStringが使える
- 軽量:JavaScriptにコンパイルした時のコードが短い
- 直感的:値そのものを使うので分かりやすい
- JSONとの親和性:APIのレスポンスなどと直接マッチする

確かにリテラル型の方がシンプルで分かりやすいですね!
実践的な使い方
React コンポーネントでの活用例
// ボタンのサイズを限定
type ButtonSize = 'small' | 'medium' | 'large';
// ボタンの種類を限定
type ButtonVariant = 'primary' | 'secondary' | 'danger';
interface ButtonProps {
size: ButtonSize;
variant: ButtonVariant;
children: string;
}
function Button({ size, variant, children }: ButtonProps) {
const className = `btn btn-${variant} btn-${size}`;
return <button className={className}>{children}</button>;
}
// 使用例
<Button size="large" variant="primary">送信</Button>
// <Button size="huge" variant="primary">送信</Button> // エラー!
API レスポンスの型定義
// APIの応答ステータスを定義
type ApiStatus = 'pending' | 'fulfilled' | 'rejected';
interface ApiResponse<T> {
status: ApiStatus;
data?: T;
error?: string;
}
// ユーザー情報のレスポンス
const userResponse: ApiResponse<{name: string, age: number}> = {
status: 'fulfilled', // この値は3つの選択肢に限定される
data: { name: 'OZ君', age: 17 }
};
const アサーションとの組み合わせ
// 通常の変数宣言
let normalVar = 'hello'; // string型(どんな文字列でもOK)
normalVar = 'world'; // OK
// const宣言
const constVar = 'hello'; // 'hello'リテラル型('hello'のみ)
// constVar = 'world'; // エラー!constなので再代入不可
// 配列やオブジェクトでのconst assertion
const colors = ['red', 'green', 'blue'] as const;
// colorsの型は readonly ['red', 'green', 'blue']
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000
} as const;
// configの各プロパティもリテラル型になる

へえ!const assertionを使うと、配列やオブジェクトの値も固定できるんですね!
まとめ

今日学んだことをまとめてみよう。
ポイント整理
- Enum型:列挙型を作れるが、数値Enumは型安全性に問題がある
- 文字列Enum:型安全だが、記述が少し冗長
- リテラル型 + ユニオン型:シンプルで分かりやすく、現在の主流
- const assertion:値を固定してリテラル型にできる
実際の開発での選択指針
- 新しいプロジェクト:リテラル型を推奨
- 既存のEnumがあるプロジェクト:文字列Enumでも問題なし
- 選択肢が多い場合:可読性を考慮してEnumも検討

よく分かりました!これからはリテラル型を中心に使ってみます!