読み取り専用の配列 (readonly array)
TypeScriptでは配列を読み取り専用(readonly)として型注釈できます。型注釈の方法は2とおりあります。1つ目はreadonlyキーワードを使う方法です。2つ目はReadonlyArray<T>を使う方法です。
readonly T[]#
配列の型注釈T[]の前にreadonlyキーワードを添えると、読み取り専用の配列型にできます。たとえば、readonly number[]と書くと、その変数の型はnumberの読み取り専用配列型になります。
typescriptconst nums: readonly number[] = [1, 2, 3];
typescriptconst nums: readonly number[] = [1, 2, 3];
ReadonlyArray<T>#
ReadonlyArray<T>のような書き方でも読み取り専用の配列型になります。たとえば、要素がnumber型の配列を読み取り専用にしたい場合、ReadonlyArray<number>と書きます。
typescriptconst nums: ReadonlyArray<number> = [1, 2, 3];
typescriptconst nums: ReadonlyArray<number> = [1, 2, 3];
readonly T[]とReadonlyArray<T>の違い#
readonly T[]とReadonlyArray<T>の違いは書き方以外にありません。どちらを使うかは書き手の好みです。開発チームとしてはどちらの書き方にするかは統一しておいたほうがよいでしょう。
読み取り専用配列の特徴#
読み取り専用の配列には、配列に対して破壊的操作をするpushメソッドやpopメソッドが、コンパイル時には無いことになります。したがって、readonly number[]型の変数numsに対して、nums.push(4)をするコードはコンパイルエラーになります。
typescriptconst nums: readonly number[] = [1, 2, 3];nums.push(4);// コンパイルエラー: Property 'push' does not exist on type 'readonly number[]'.(2339)
typescriptconst nums: readonly number[] = [1, 2, 3];nums.push(4);// コンパイルエラー: Property 'push' does not exist on type 'readonly number[]'.(2339)
これは、破壊的操作系のメソッドを呼び出そうとするコードがTypeScriptコンパイラーに警告されるだけです。配列オブジェクトからpushメソッドを削除しているわけではありません。なので、JavaScript実行時にはpushメソッドが残っている状態になります。
typescriptconst nums: readonly number[] = [1, 2, 3];console.log("pop" in nums); //=> true
typescriptconst nums: readonly number[] = [1, 2, 3];console.log("pop" in nums); //=> true
メソッドは削除されるわけではないので、コンパイルエラーを無視して実行してみると、読み取り専用型でも配列を書き換えることはできます。
typescriptconst nums: readonly number[] = [1, 2, 3];// @ts-ignorenums.push(4); // 本来コンパイルエラーになるが無視するconsole.log(nums); //=> [1, 2, 3, 4]
typescriptconst nums: readonly number[] = [1, 2, 3];// @ts-ignorenums.push(4); // 本来コンパイルエラーになるが無視するconsole.log(nums); //=> [1, 2, 3, 4]
読み取り専用配列を配列に代入する#
TypeScriptの読み取り専用配列を普通の配列に代入することはできません。代入しようとするとコンパイルエラーになります。
typescriptconst 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)
typescriptconst 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)
これは、普通の配列はpushやpopなどのメソッドが必要なのに、読み取り専用配列にはそれが無いことになっているためです。どうしても読み取り専用配列を普通の配列に代入したいときは、型アサーション(type assertion)を使う方法があります。
typescriptconst readonlyNumbers: readonly number[] = [1, 2, 3];const writableNumbers: number[] = readonlyNumbers as number[];// ^^^^^^^^^^^ 型アサーション
typescriptconst readonlyNumbers: readonly number[] = [1, 2, 3];const writableNumbers: number[] = readonlyNumbers as number[];// ^^^^^^^^^^^ 型アサーション
逆のパターンとして、普通の配列を読み取り専用配列に代入することは可能です。