TypeScript | 基礎文法:変数・基本型 – リテラル型とは

TypeScript
スポンサーリンク

リテラル型とは何か(ざっくりイメージ)

リテラル型は、「特定の値そのものを型として扱う」ための仕組みです。
普通の 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'
TypeScript

y の型は number ではなく「値が 1 のときだけ許される型」です。
この「特定の値だけを許す」というのが、リテラル型の本質です。


どんなものがリテラル型になり得るか

リテラル型として表現できるのは、主にプリミティブ型の「特定の値」です。
代表的なのは、真偽値の true / false、数値リテラル、文字列リテラルです。

const isTrue: true = true;
const num: 123 = 123;
const str: "foo" = "foo";
TypeScript

ここで isTrue は「true だけを許す型」、num は「123 だけを許す型」、str は「”foo” だけを許す型になっています。
普通の booleannumberstring よりも、ずっと狭い世界を表現しているイメージです。

TypeScriptの型システム的には、「"Hello World"string の一種だが、string"Hello World" ではない」という関係になっています。
つまり、「広い型(string)」の中に、「特定の値(”Hello World”)」というより具体的なサブタイプがある、という構造です。


const とリテラル型の関係(なぜ const でよく出てくるのか)

const で宣言した変数は、「値が変わらない」とコンパイラに約束します。
その結果、TypeScript は「この変数はこの値以外にならない」と判断し、リテラル型として扱えるようになります。

const status = "success";
// status の型は "success"(文字列リテラル型)

let status2 = "success";
// status2 の型は string(将来別の文字列が入るかもしれない)
TypeScript

const の場合、「この変数は一生 "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
  }
}
TypeScript

type"C" を渡そうとした瞬間にエラーになるので、「想定外の状態」が紛れ込む余地がぐっと減ります。


初心者がまず掴んでおきたいリテラル型の感覚

リテラル型は、こういう感覚で捉えるとスッと入ってきます。

「string は“文字列なら何でもOK”だけど、"success" は“この文字列だけOK”という型にできる」
「number は“数なら何でもOK”だけど、1 | 2 | 3 のように“この3つだけ”に絞れる」
「const で宣言した値は、そのまま“その値の型”として扱えることが多い」

そして実務では、
状態(status)
モード(mode)
テーマ(theme)
種別(type, kind)
のような「限られた選択肢の中から選ぶ値」を、リテラル型+ユニオン型で表現することがとても多いです。

「ここ、本当は "light""dark" しか来ないはずなんだよな」と思ったら、
string ではなく "light" | "dark" と書いてみる。

それだけで、あなたのTypeScriptは一段階カチッと安全側に寄ります。

タイトルとURLをコピーしました