こんにちは、かつコーチです。
前回はunion型とintersection型を使って、複数の型を組み合わせる方法を解説しました。
その中で何度か登場した「リテラル型」について、今回はあらためて詳しく掘り下げていきます。
「特定の値しか受け付けない型」を作れるようになると、想定外の値の混入をコンパイル時に防げるようになり、コードの安全性が大きく向上します。
リテラル型とは?
通常の型との違い
リテラル型とは、string や number のような広い型ではなく、「特定の値そのもの」を型として扱う仕組みです。
let status1: string = "success"; // stringならどんな文字列でもOK
let status2: "success" = "success"; // "success"という値しか受け付けない
status2 = "error"; // エラー:"success"以外は代入できない
string 型は「文字列であれば何でも入る」という広い型ですが、"success" のようにダブルクォートで値そのものを型として書くと、その値以外は代入できなくなります。
数値や真偽値でも同様にリテラル型を作れます。
let count: 1 = 1;
let isActive: true = true;
リテラル型の使いどころ
union型と組み合わせて選択肢を限定する
リテラル型は単体で使うよりも、union型と組み合わせて「決まった選択肢の中の一つ」を表現する場面でよく使われます。
type Direction = "up" | "down" | "left" | "right";
function move(direction: Direction): void {
console.log(`${direction}に移動します`);
}
move("up"); // OK
move("diagonal"); // エラー:Direction型に含まれない値
Direction 型に含まれる4つの文字列以外は代入できないため、タイプミスや仕様外の値が紛れ込むのをコンパイル時に防げます。
string 型で受け取っていた場合は、"upp" のような単純なタイプミスも実行時までエラーに気づけませんが、リテラル型のunionならその場で分かります。
定数のオブジェクトから型を作る
選択肢を毎回リテラル型として手書きするのではなく、実際の値の一覧(オブジェクトや配列)から型を自動生成する方法もよく使われます。
const STATUS = {
LOADING: "loading",
SUCCESS: "success",
ERROR: "error",
} as const;
type Status = (typeof STATUS)[keyof typeof STATUS];
// Status型は "loading" | "success" | "error" と同じ意味になる
function showMessage(status: Status): string {
switch (status) {
case STATUS.LOADING:
return "読み込み中です";
case STATUS.SUCCESS:
return "成功しました";
case STATUS.ERROR:
return "エラーが発生しました";
}
}
console.log(showMessage(STATUS.SUCCESS)); // "成功しました"
as const をつけることで、STATUS オブジェクトの各プロパティの値が string ではなく、それぞれのリテラル型として扱われるようになります。
typeof STATUS でオブジェクトの型を取得し、keyof でそのキー一覧を取り、さらにインデックスアクセス([keyof typeof STATUS])で値の型一覧を取り出す、という組み合わせです。
少しテクニカルですが、「実際に使う値の一覧」と「その値を表す型」を一箇所で管理できるようになるため、値の追加・変更に強い書き方です。
as const の役割をもう少し詳しく
as const をつけない場合との違い
as const がどのように効いているのか、比較して確認してみましょう。
const obj1 = { status: "success" };
// objの型は { status: string } と推論される(広い型)
const obj2 = { status: "success" } as const;
// obj2の型は { readonly status: "success" } と推論される(狭いリテラル型)
as const をつけないと、TypeScriptは「今後この値は変わるかもしれない」という前提で、プロパティの型を広い string として推論します。
as const をつけると、「この値はこれ以上変わらない」という意図が伝わり、プロパティの型がそのリテラル型に固定され、さらに readonly も自動的につきます。
「値を限定したい」という意図がある場面では、この as const を活用できるかどうかが大きな分かれ目になります。
つまずきやすいポイント:配列を渡したらリテラル型が広がってしまった
実際につまずいた話
私が as const を覚えたてのころ、配列の要素をそのままリテラル型のunionとして使おうとして、なかなかうまくいかない経験をしました。
やりたかったのは、許可するロール(役割)の一覧を配列で定義し、そこから型を自動生成することでした。
最初は as const をつけずに配列を定義してしまい、期待した「限定されたリテラル型のunion」ではなく、ただの string[] 型になってしまうという事態にぶつかりました。
型エラーが出るべき場面でエラーが出ず、「あれ、リテラル型になっていない」と気づくまでに少し時間がかかったのを覚えています。
原因は単純で、配列リテラルに as const をつけ忘れていたことでした。
Before/Afterで確認する
❌ Before:as constをつけずに配列から型を作ろうとする
const ROLES = ["admin", "editor", "viewer"];
// as constがないため、ROLESの型はstring[]と推論される
type Role = (typeof ROLES)[number];
// Role型はstring型になってしまい、限定できていない
function checkRole(role: Role): void {
console.log(`${role}権限で処理します`);
}
checkRole("guest"); // 本来は許可していない値だが、コンパイルエラーにならない
ROLES に as const がついていないため、配列の型は string[] と推論されてしまい、Role 型も結局ただの string になります。
その結果、"admin" や "editor" 以外の任意の文字列も受け入れてしまいます。
✅ After:as constをつけてリテラル型のunionを作る
const ROLES = ["admin", "editor", "viewer"] as const;
// ROLESの型は readonly ["admin", "editor", "viewer"] になる
type Role = (typeof ROLES)[number];
// Role型は "admin" | "editor" | "viewer" になる
function checkRole(role: Role): void {
console.log(`${role}権限で処理します`);
}
checkRole("admin"); // OK
checkRole("guest"); // エラー:Role型に含まれない値
as const を配列につけることで、ROLES の型が「読み取り専用のタプル」として固定され、(typeof ROLES)[number] で取り出した Role 型も正しくリテラル型のunionになります。
配列や設定値から型を自動生成したいときは、as const を忘れていないか必ず確認する習慣をつけておくと、このつまずきを避けられます。
応用:テンプレートリテラル型で文字列パターンを表現する
決まったパターンの文字列を型で表現する
リテラル型は、テンプレートリテラルと組み合わせることで、より柔軟なパターンを表現できます。
type Size = "sm" | "md" | "lg";
type Color = "red" | "blue";
type ButtonClass = `btn-${Size}-${Color}`;
// "btn-sm-red" | "btn-sm-blue" | "btn-md-red" | ... のすべての組み合わせになる
const className: ButtonClass = "btn-md-blue"; // OK
const invalidClassName: ButtonClass = "btn-xl-blue"; // エラー:"xl"はSize型に含まれない
`btn-${Size}-${Color}` のようにバッククォートを使ったテンプレートリテラル型を書くと、Size と Color のすべての組み合わせを網羅したリテラル型のunionが自動的に作られます。
CSSのクラス名やAPIのエンドポイント文字列のように、決まったパターンで組み合わさる文字列を扱うときに役立つテクニックです。
まとめ
この記事のポイント
- リテラル型は「特定の値そのもの」を型として扱う仕組み
- union型と組み合わせることで、決まった選択肢の中の一つに値を限定できる
as constをつけることで、値の型を広い型ではなくリテラル型として固定できる- 配列や定数オブジェクトから型を自動生成する際は
as constの付け忘れに注意する - テンプレートリテラル型を使うと、決まったパターンの文字列を型として表現できる
次に読むべき記事
リテラル型まで理解できたら、次は type と interface の違いと使い分けを学んでいきましょう。
→ 次の記事:typeとinterface、結局どちらを使うべき?