こんにちは!TypeScriptの型システムの世界へようこそ。
日々たくさんのコードを書いていると、「この設定値、うっかりどこかで書き換えてしまってバグった…!」なんて経験、ありませんか?
今回は、そんなうっかりミスをTypeScriptのコンパイラに完全におまかせして予防する、超実用的なテクニック「`readonly`タプルによるイミュータブルな設定値管理」についてお話ししていきますね。
ここをクリアすれば、あなたの書くコードの安全性は一段と跳ね上がります。一緒にバッチリマスターしていきましょう!
—
1. なぜ「設定値の変更」はバグの温床になるのか?
アプリケーションを作るとき、例えば「ユーザーの権限一覧」や「APIのエンドポイントのステータス」といった、絶対に途中で変わってはいけない定数のリストを定義することがよくありますよね。
普通の配列(`string[]` や `number[]`)を使ってこう書いたとします。
// 普通の配列を使った設定値の定義
const USER_ROLES = [“admin”, “editor”, “viewer”];
一見、問題なさに思えますが、TypeScript(およびJavaScript)の世界では、`const`で宣言した配列であっても、中身の要素を追加・変更・削除することは自由自在にできてしまいます。
// うっかり書き換えられてしまう例
USER_ROLES.push(“super-admin”); // 動いてしまう!
USER_ROLES[0] = “hacker”; // 書き換えられてしまう!
これでは、「絶対に変わらないはずの設定値」という前提がコードのどこかで崩れてしまい、予測不能なバグを生む原因になってしまいますよね。
—
2. 救世主「readonlyタプル」の登場
ここで登場するのが、「タプル(Tuple)」と「`readonly`修飾子」の組み合わせです。
まずはそれぞれの言葉を整理しておきましょう。
- タプル: 要素の「数」と「順番ごとの型」が厳密に固定された配列。
- `readonly`: 「読み取り専用」にし、後からの変更を一切許さないようにする型修飾子。
この2つを組み合わせると、TypeScriptの型システムはこう叫びます。
「この配列は、この順番・この値のままで一生生きていきなさい。外から指一本たりとも触らせない!」
基本的な書き方
書き方はとてもシンプルです。型アノテーション(または `as const`)を使います。
// 1. readonly とタプル型を明示的に指定する方法
const USER_ROLES: readonly string[] = [“admin”, “editor”, “viewer”];
// 2. さらに強力な「as const(constアセッション)」を使う方法(実務ではこっちが主流!)
const APP_CONFIG = [“production”, 3000, true] as const;
特に `as const` を使うと、要素が「変更不可能な読み取り専用(readonly)」になるだけでなく、それぞれの値が `string` や `number` という「ざっくりした型」ではなく、`”production”` や `3000` といった「そのリテラル値そのものの型」に固定されます。ここがチート級に強力なポイントです。
—
3. 実践!安全な設定値管理のコード例
では、実際の開発現場をイメージして、`readonly`タプルを使った堅牢な設定値管理のコードを見てみましょう。
/
- アプリケーションの許可されたテーマカラー一覧
- as const を使うことで、readonly なタプル型として推論されます
/
const ALLOWED_THEMES = [“light”, “dark”, “system”] as const;
// 【型パールの魔法】
// ALLOWED_THEMES の型は string[] ではなく、
// readonly [“light”, “dark”, “system”] という厳密な型になります。
// 設定値から「取り得るテーマの型」を自動で抽出する
type Theme = typeof ALLOWED_THEMES[number];
// ↑ これで type Theme = “light” | “dark” | “system” という型が作られます!すごいですよね?
function applyTheme(theme: Theme) {
console.log(`Applying theme: ${theme}`);
}
// 正常系:定義された値を使う
applyTheme(“dark”); // OK!
// ————————————————–
// ❌ ここからはコンパイルエラーになる世界です
// ————————————————–
// 1. 存在しないテーマを指定した場合(型エラー)
// applyTheme(“retro”);
// ギューン! ❌ Argument of type ‘”retro”‘ is not assignable to parameter of type ‘Theme’.
// 2. 配列の要素を書き換えようとした場合(型エラー)
// ALLOWED_THEMES[0] = “neon”;
// ギューン! ❌ Cannot assign to ‘0’ because it is a read-only property.
// 3. 要素を追加しようとした場合(型エラー)
// ALLOWED_THEMES.push(“pink”);
// ギューン! ❌ Property ‘push’ does not exist on type ‘readonly [“light”, “dark”, “system”]’.
コードのここがポイント!
`typeof ALLOWED_THEMES[number]` という書き方、見慣れないかもしれませんが、TypeScriptの中級者以上へのパスポートとも言えるイディオムです。
「readonlyタプルのすべての要素の型をユニオン型(`|`)として取り出す」という魔法のような操作で、設定値の追加・変更があった際にも、型をわざわざ書き直す必要がなくなります(DRY原則の徹底ですね!)。
—
4. 初学者が陥りやすい文法エラーと罠
ここで、初心者の頃に誰もが一度はハマるポイントをいくつかご紹介しておきますね。
罠1: `const` で宣言したのにエラーにならない?
const roles = [“admin”, “editor”];
roles.push(“viewer”); // あれ?エラーにならない!?
「あれ? `const` ってイミュータブルにするための機能じゃないの?」と思われがちですが、JavaScriptの `const` は「変数への再代入(`roles = […]` のような書き換え)」を防ぐものであって、配列の中身が変わるのを防ぐものではありません。
配列の中身までガチガチに固めたいときは、必ず `readonly` や `as const` をセットで使う必要があるんです。
罠2: `as const` をつけた後にメソッドを呼ぼうとして焦る
`as const` をつけると、配列の要素が「その値そのもの」になるため、通常の可変配列用のメソッド(`push`, `pop`, `sort` など、配列を破壊的に変更するメソッド)が TypeScript の型定義レベルで消滅します。
「あれ? `push` が補完に出てこないんだけど!バグ!?」と思ったら、それは正常な動作です。イミュータブルな世界へようこそ、というサインですので安心してください。
—
まとめ
いかがだったでしょうか?
- 普通の配列や `const` だけでは、うとした値の書き換えを防げない。
- `readonly` 修飾子や `as const` を組み合わせることで、「値の変更」「要素の追加・削除」をコンパイル時に完全に封じ込めることができる。
- `typeof CONFIG[number]` を使えば、設定値から美しい型を自動生成できる。
この `readonly` タプルを使いこなせるようになると、コードの安全性と保守性が劇的に向上します。「あ、私のコード、堅牢に作られているな」という確信を持って開発できるようになりますよ。
ここをクリアしたあなたなら、TypeScriptの型システムの基本はもうバッチリマスターできています!
ぜひ、今日のコードから設定値の `as const` 化、試してみてくださいね。それでは、また次の記事でお会いしましょう!