TypeScript | 基礎文法:配列・タプル – readonly配列

TypeScript
スポンサーリンク

readonly配列とは何か(「中身を書き換えられない配列」)

readonly配列は、一言でいうと「中身を変更できない配列」です。
「参照はするけど、追加・削除・書き換えはさせたくない」というときに使います。

const nums: readonly number[] = [1, 2, 3];

// nums.push(4);    // エラー
// nums[0] = 10;    // エラー
const x = nums[0];   // 読み取りはOK
TypeScript

普通の number[] なら push や代入ができますが、readonly number[] にすると「読むだけOK・書き込み禁止」というモードになります。
「この配列は“定義したあと変えない前提”なんだよ」という意図を、型で表現するための仕組みです。


readonly配列の書き方と基本的な挙動

readonly T[] と ReadonlyArray<T> の2通り

readonly配列には、主に2つの書き方があります。

const a: readonly string[] = ["a", "b"];
const b: ReadonlyArray<string> = ["a", "b"];
TypeScript

どちらも意味は同じで、「string の読み取り専用配列」です。
実務では readonly T[] の方がよく使われますが、ReadonlyArray<T> も読めるようになっておけば十分です。

何が禁止されて、何が許されるのか

const nums: readonly number[] = [1, 2, 3];

// 変更系は全部ダメ
// nums.push(4);      // エラー
// nums.pop();        // エラー
// nums[0] = 10;      // エラー

// 読み取り系はOK
const first = nums[0];
const len = nums.length;
const doubled = nums.map(n => n * 2); // これはOK(新しい配列を返すだけ)
TypeScript

ポイントは、「配列そのものを変える操作」が禁止されるだけで、
map, filter, slice のように新しい配列を返すメソッドは普通に使えるということです。
「元を壊さないならOK」「元を壊すならNG」という線引きになっています。


readonly配列が役に立つ場面

「渡した先で勝手に書き換えられたくない」引数

関数の引数に readonly配列を使うと、「この関数は配列を壊さない」という約束を型で表現できます。

function sum(nums: readonly number[]): number {
  // nums.push(100); // エラー:壊せない
  return nums.reduce((acc, n) => acc + n, 0);
}

const xs = [1, 2, 3];
const total = sum(xs);
TypeScript

ここで sum の中から nums を変更しようとするとコンパイルエラーになります。
呼び出し側から見ると、「この関数に配列を渡しても、中身をいじられない」と安心して使えるわけです。

定数テーブル・マスターデータとの相性がいい

「アプリ全体で使う定数の一覧」のような配列は、基本的に変更されるべきではありません。

const ROLES: readonly string[] = ["admin", "user", "guest"];
TypeScript

こうしておけば、どこかでうっかり ROLES.push("root") のようなコードを書いたときに、コンパイル時に止めてくれます。
「変えちゃダメなもの」を readonly にしておくのは、バグを防ぐうえでかなり効きます。


readonly配列と as const の関係

as const は「readonly配列+リテラル型」にしてくれる

as const を配列リテラルに付けると、その配列は自動的に readonly になります。

const COLORS = ["red", "green", "blue"] as const;
// 型: readonly ["red", "green", "blue"]

// COLORS.push("yellow"); // エラー
// COLORS[0] = "black";   // エラー
TypeScript

ここでは「タプル+リテラル型」になっていますが、
少なくとも「書き換え禁止の配列」という意味では、readonly配列と同じ性質を持っています。

as const は「この配列は定義そのものが意味を持つ。変えたくない」というときの最強コンボで、
readonly配列の考え方とかなり親和性が高いです。


readonly配列と普通の配列の相互変換の感覚

普通の配列 → readonly配列 には“広い型から狭い型”として代入できる

const xs: number[] = [1, 2, 3];
const ys: readonly number[] = xs; // OK
TypeScript

「変更可能な配列」を「読み取り専用として扱う」のは安全なので、これは許されます。
「可変なものを、あえて不変として扱う」イメージです。

readonly配列 → 普通の配列 は基本的にNG(キャストが必要)

const xs: readonly number[] = [1, 2, 3];
// const ys: number[] = xs; // エラー
TypeScript

「読み取り専用のもの」を「変更可能」として扱うのは危険なので、そのままでは代入できません。
どうしても必要なら、コピーして新しい配列を作るのが安全です。

const ys: number[] = [...xs]; // スプレッドでコピー
ys.push(4); // これはOK(ys は普通の配列)
TypeScript

「元は守りつつ、必要なところだけコピーして自由にいじる」というスタイルは、バグを減らすうえでもかなり有効です。


初心者がまず身につけたい readonly配列の感覚

readonly配列の本質は、「この配列は“壊さない前提”で扱いたい」という意図を型に刻むことです。

関数の引数に readonly T[] を使うと、「この関数は配列を変更しない」と宣言できる
定数配列を readonly にしておくと、「うっかり変更」をコンパイル時に防げる
as const と組み合わせると、「定義そのものが仕様の配列」をガチガチに守れる

最初のうちは、
「この配列、変えられたら困るよな」と思ったら readonly を付けてみる。
「この関数、配列を読むだけだよな」と思ったら、引数を readonly T[] にしてみる。

その小さな一歩だけで、TypeScript があなたの意図をちゃんと理解して、
「そこは壊させないよ」と守ってくれるようになります。

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