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

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

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

readonly T[]

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

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

ReadonlyArray<T>

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

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

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

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

読み取り専用配列の特徴

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

ts
const nums: readonly number[] = [1, 2, 3];
nums.push(4);
Property 'push' does not exist on type 'readonly number[]'.2339Property 'push' does not exist on type 'readonly number[]'.
ts
const nums: readonly number[] = [1, 2, 3];
nums.push(4);
Property 'push' does not exist on type 'readonly number[]'.2339Property 'push' does not exist on type 'readonly number[]'.

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

ts
const nums: readonly number[] = [1, 2, 3];
console.log("push" in nums);
true
ts
const nums: readonly number[] = [1, 2, 3];
console.log("push" in nums);
true

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

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

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

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

ts
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[]'.4104The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
ts
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[]'.4104The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.

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

ts
const readonlyNumbers: readonly number[] = [1, 2, 3];
const writableNumbers: number[] = readonlyNumbers as number[];
ts
const readonlyNumbers: readonly number[] = [1, 2, 3];
const writableNumbers: number[] = readonlyNumbers as number[];

📄️ 型アサーション「as」

TypeScriptには、型推論を上書きする機能があります。その機能を型アサーション(type assertion)と言います。

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

関連情報

📄️ 配列の破壊的操作

JavaScriptの配列メソッドには、破壊的なメソッドと非破壊的なメソッドの2種類があります。特に、破壊的なメソッドは注意深く使う必要があります。

📄️ オブジェクト型のreadonlyプロパティ

TypeScriptでは、オブジェクトのプロパティを読み取り専用にすることができます。読み取り専用にしたいプロパティにはreadonly修飾子をつけます。読み取り専用のプロパティに値を代入しようとすると、TypeScriptコンパイラーが代入不可の旨を警告するようになります。

📄️ Readonly<T>

全プロパティを読み取り専用にする

📄️ constアサーション「as const」

オブジェクトリテラルの末尾にas constを記述すればプロパティがreadonlyでリテラルタイプで指定した物と同等の扱いになります。