数値リテラル型とは何か
数値リテラル型は、「特定の数値だけを許可する型」です。
普通の number 型は「どんな数値でもOK」ですが、数値リテラル型は「この変数には 1 しか入れてはいけない」「0 か 1 のどちらかだけ」といった、取り得る値をきわめて限定した型を表現します。
let n1: number;
n1 = 1;
n1 = 100; // どんな数値でもOK
let n2: 1;
n2 = 1; // OK
// n2 = 2; // エラー:Type '2' is not assignable to type '1'
TypeScriptn2 の型は number ではなく、「値が 1 のときだけ許される型」です。
これが数値リテラル型の一番シンプルなイメージです。
数値リテラル型とユニオン型の組み合わせ
「取り得る数値が決まっている」場面を型で表現する
数値リテラル型が本領発揮するのは、ユニオン型と組み合わせたときです。
たとえば、「サイコロの目は 1〜6 のどれか」という制約を、そのまま型にしてみます。
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
let d: Dice = 3; // OK
// d = 7; // エラー:Type '7' is not assignable to type 'Dice'
TypeScriptDice は「1, 2, 3, 4, 5, 6 のいずれか」という型です。
7 や 0 を代入しようとすると、コンパイル時に止められます。
同じように、「フラグとして 0 か 1 だけを使いたい」といった場面も表現できます。
type Bit = 0 | 1;
let b: Bit = 0;
b = 1;
// b = 2; // エラー
TypeScript現実世界の「取り得る値が限られている数」を、そのまま型に落とし込めるのが数値リテラル型の強みです。
const と数値リテラル型の関係
const で宣言すると「その値の型」になる
const で数値を宣言すると、その変数は多くの場合「数値リテラル型」として扱われます。
const ONE = 1;
// ONE の型は 1
let two = 2;
// two の型は number
TypeScriptconst ONE は再代入できないので、「この変数は一生 1 のまま」とコンパイラは判断できます。
その結果、型としても「1」というリテラル型にまで絞り込めます。
一方 let two は将来 3 や 4 になるかもしれないので、「number 全体」として扱われます。
定数と型をリンクさせる
この性質を使うと、「定数」と「型」をきれいに結びつけられます。
const LEVEL_EASY = 1 as const;
const LEVEL_NORMAL = 2 as const;
const LEVEL_HARD = 3 as const;
type Level = typeof LEVEL_EASY | typeof LEVEL_NORMAL | typeof LEVEL_HARD;
let lv: Level = LEVEL_NORMAL;
// lv = 4; // エラー
TypeScripttypeof LEVEL_EASY は「1 型」、typeof LEVEL_NORMAL は「2 型」です。
それらをユニオンした Level は、「1, 2, 3 のどれか」という型になります。
定数の値と型定義がズレにくくなるので、実務でもよく使うパターンです。
数値リテラル型が守ってくれるもの
「本当はあり得ない値」をコンパイル時に排除する
数値リテラル型を使うと、「number では広すぎて防げないバグ」をかなり潰せます。
たとえば、難易度を 1〜3 の数値で表すとします。
type Difficulty = 1 | 2 | 3;
function setDifficulty(d: Difficulty) {
// ...
}
setDifficulty(1);
setDifficulty(3);
// setDifficulty(4); // エラー
TypeScriptDifficulty がただの number だったら、4 や 100 も通ってしまいます。
数値リテラル型にしておけば、「その値は許可されていない」とコンパイル時に教えてくれます。
switch 文との相性
数値リテラル型は switch 文とも相性がいいです。
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
function describe(d: Dice): string {
switch (d) {
case 1:
return "one";
case 2:
return "two";
case 3:
return "three";
case 4:
return "four";
case 5:
return "five";
case 6:
return "six";
}
}
TypeScriptDice が 1〜6 に限定されているので、「7 のケースを書き忘れた」といった心配はそもそもありません。
将来、もし値のパターンを増やしたくなったら、type Dice の定義を変えるだけで、足りない分岐があればコンパイラが教えてくれます。
初心者がまず掴んでおきたい数値リテラル型の感覚
数値リテラル型は、こう捉えると分かりやすいです。
「number は“どんな数でもOK”」
「数値リテラル型は“この数だけOK”」
「複数の数値リテラル型を | でつなぐと、“この中のどれかだけOK”という型になる」
そして、実際のコードの中で、
サイコロの目(1〜6)
難易度レベル(1〜3)
フラグ(0 / 1)
のように、「取り得る数値が限られている場面」が出てきたら、
「これ、ただの number じゃなくて数値リテラル型にできないかな?」と一度考えてみる。
その一歩を踏むだけで、「本当はあり得ない値」が紛れ込む余地を、型レベルでかなり削ることができます。
