メインコンテンツまでスキップ

読み取り専用の配列 (readonly array)

TypeScriptでは配列を読み取り専用(readonly)として型注釈できます。型注釈の方法は2とおりあります。1つ目はreadonlyキーワードを使う方法です。2つ目はReadonlyArray<T>を使う方法です。

readonly T[]#

配列の型注釈T[]の前にreadonlyキーワードを添えると、読み取り専用の配列型にできます。たとえば、readonly number[]と書くと、その変数の型はnumberの読み取り専用配列型になります。

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

ReadonlyArray<T>#

ReadonlyArray<T>のような書き方でも読み取り専用の配列型になります。たとえば、要素がnumber型の配列を読み取り専用にしたい場合、ReadonlyArray<number>と書きます。

typescript
const nums: ReadonlyArray<number> = [1, 2, 3];
typescript
const nums: ReadonlyArray<number> = [1, 2, 3];

readonly T[]とReadonlyArray<T>の違い#

readonly T[]ReadonlyArray<T>の違いは書き方以外にありません。どちらを使うかは書き手の好みです。開発チームとしてはどちらの書き方にするかは統一しておいたほうがよいでしょう。

読み取り専用配列の特徴#

読み取り専用の配列には、配列に対して破壊的操作をするpushメソッドやpopメソッドが、コンパイル時には無いことになります。したがって、readonly number[]型の変数numsに対して、nums.push(4)をするコードはコンパイルエラーになります。

typescript
const nums: readonly number[] = [1, 2, 3];
nums.push(4);
// コンパイルエラー: Property 'push' does not exist on type 'readonly number[]'.(2339)
typescript
const nums: readonly number[] = [1, 2, 3];
nums.push(4);
// コンパイルエラー: Property 'push' does not exist on type 'readonly number[]'.(2339)

これは、破壊的操作系のメソッドを呼び出そうとするコードがTypeScriptコンパイラーに警告されるだけです。配列オブジェクトからpushメソッドを削除しているわけではありません。なので、JavaScript実行時にはpushメソッドが残っている状態になります。

typescript
const nums: readonly number[] = [1, 2, 3];
console.log("pop" in nums); //=> true
typescript
const nums: readonly number[] = [1, 2, 3];
console.log("pop" in nums); //=> true

メソッドは削除されるわけではないので、コンパイルエラーを無視して実行してみると、読み取り専用型でも配列を書き換えることはできます。

typescript
const nums: readonly number[] = [1, 2, 3];
// @ts-ignore
nums.push(4); // 本来コンパイルエラーになるが無視する
console.log(nums); //=> [1, 2, 3, 4]
typescript
const nums: readonly number[] = [1, 2, 3];
// @ts-ignore
nums.push(4); // 本来コンパイルエラーになるが無視する
console.log(nums); //=> [1, 2, 3, 4]

読み取り専用配列を配列に代入する#

TypeScriptの読み取り専用配列を普通の配列に代入することはできません。代入しようとするとコンパイルエラーになります。

typescript
const readonlyNumbers: readonly number[] = [1, 2, 3];
const writableNumbers: number[] = readonlyNumbers;
// コンパイルエラー: The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.(4104)
typescript
const readonlyNumbers: readonly number[] = [1, 2, 3];
const writableNumbers: number[] = readonlyNumbers;
// コンパイルエラー: The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.(4104)

これは、普通の配列はpushpopなどのメソッドが必要なのに、読み取り専用配列にはそれが無いことになっているためです。どうしても読み取り専用配列を普通の配列に代入したいときは、型アサーション(type assertion)を使う方法があります。

typescript
const readonlyNumbers: readonly number[] = [1, 2, 3];
const writableNumbers: number[] = readonlyNumbers as number[];
// ^^^^^^^^^^^ 型アサーション
typescript
const readonlyNumbers: readonly number[] = [1, 2, 3];
const writableNumbers: number[] = readonlyNumbers as number[];
// ^^^^^^^^^^^ 型アサーション

逆のパターンとして、普通の配列を読み取り専用配列に代入することは可能です。

関連情報#