リテラル型とは何か(ざっくりイメージ)
リテラル型は、「特定の値そのものを型として扱う」ための仕組みです。
普通の string 型は「どんな文字列でもOK」、number 型は「どんな数値でもOK」ですが、リテラル型を使うと「この変数には 1 しか入れてはいけない」「この変数には "success" か "error" しか入れてはいけない」といった、取り得る値を極限まで絞り込んだ型を作れます。
let x: number;
x = 1; // OK
x = 100; // OK(number なので何でも可)
let y: 1;
y = 1; // OK
// y = 100; // エラー:Type '100' is not assignable to type '1'
TypeScripty の型は number ではなく「値が 1 のときだけ許される型」です。
この「特定の値だけを許す」というのが、リテラル型の本質です。
どんなものがリテラル型になり得るか
リテラル型として表現できるのは、主にプリミティブ型の「特定の値」です。
代表的なのは、真偽値の true / false、数値リテラル、文字列リテラルです。
const isTrue: true = true;
const num: 123 = 123;
const str: "foo" = "foo";
TypeScriptここで isTrue は「true だけを許す型」、num は「123 だけを許す型」、str は「”foo” だけを許す型になっています。
普通の boolean や number や string よりも、ずっと狭い世界を表現しているイメージです。
TypeScriptの型システム的には、「"Hello World" は string の一種だが、string は "Hello World" ではない」という関係になっています。
つまり、「広い型(string)」の中に、「特定の値(”Hello World”)」というより具体的なサブタイプがある、という構造です。
const とリテラル型の関係(なぜ const でよく出てくるのか)
const で宣言した変数は、「値が変わらない」とコンパイラに約束します。
その結果、TypeScript は「この変数はこの値以外にならない」と判断し、リテラル型として扱えるようになります。
const status = "success";
// status の型は "success"(文字列リテラル型)
let status2 = "success";
// status2 の型は string(将来別の文字列が入るかもしれない)
TypeScriptconst の場合、「この変数は一生 "success" のまま」と分かっているので、型としても "success" というリテラル型にまで絞り込めます。
一方 let は再代入される可能性があるので、「string 全体」として扱われます。
この「const で宣言した値が、そのままリテラル型になる」という性質が、後でユニオン型などと組み合わせるときに非常に効いてきます。
リテラル型の典型的な使いどころ(ユニオン型との組み合わせ)
リテラル型は、単体で使うよりも「ユニオン型」と組み合わせて使うと真価を発揮します。
たとえば、「状態が "idle" | "loading" | "success" | "error" のどれか」という型を作りたいとします。
type Status = "idle" | "loading" | "success" | "error";
let status: Status = "idle";
status = "loading";
status = "success";
// status = "done"; // エラー:Type '"done"' is not assignable to type 'Status'
TypeScriptここで Status は、「4つの文字列リテラル型のユニオン」です。"done" のような、想定していない文字列を代入しようとするとコンパイルエラーになります。
同じように、数値でも使えます。
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
let d: Dice = 3;
// d = 7; // エラー
TypeScript「サイコロの目は 1〜6 のどれか」という現実世界の制約を、そのまま型として表現できているわけです。
リテラル型がもたらす型安全性の「一段階上の守り」
リテラル型の一番おいしいところは、「string や number では広すぎて防げないバグを、型レベルで防げる」ことです。
例えば、API のレスポンスで "success" / "error" / "pending" の3種類だけが返ってくるとします。
type ApiStatus = "success" | "error" | "pending";
function handleStatus(status: ApiStatus) {
if (status === "success") {
// ...
} else if (status === "error") {
// ...
} else {
// pending の場合
}
}
TypeScriptここで status の型がただの string だったら、 "succes" のようなタイプミスも通ってしまいます。
でもリテラル型+ユニオン型にしておけば、「その文字列は許可されていない」とコンパイル時に止められます。
UI の出し分けでも同じです。
type CardType = "A" | "B";
function renderCard(type: CardType) {
if (type === "A") {
// AパターンのUI
} else {
// BパターンのUI
}
}
TypeScripttype に "C" を渡そうとした瞬間にエラーになるので、「想定外の状態」が紛れ込む余地がぐっと減ります。
初心者がまず掴んでおきたいリテラル型の感覚
リテラル型は、こういう感覚で捉えるとスッと入ってきます。
「string は“文字列なら何でもOK”だけど、"success" は“この文字列だけOK”という型にできる」
「number は“数なら何でもOK”だけど、1 | 2 | 3 のように“この3つだけ”に絞れる」
「const で宣言した値は、そのまま“その値の型”として扱えることが多い」
そして実務では、
状態(status)
モード(mode)
テーマ(theme)
種別(type, kind)
のような「限られた選択肢の中から選ぶ値」を、リテラル型+ユニオン型で表現することがとても多いです。
「ここ、本当は "light" と "dark" しか来ないはずなんだよな」と思ったら、string ではなく "light" | "dark" と書いてみる。
それだけで、あなたのTypeScriptは一段階カチッと安全側に寄ります。
