readonly配列にpushしようとするとdoes not exist on typeになる理由
readonly number[]のように宣言した配列型には、push・pop・splice等の破壊的(要素を変更する)メソッドがそもそも型として存在しません。呼び出そうとした時点で「そのメソッドは無い」というエラーになります。
エラーメッセージの読み方
main.ts(2,9): error TS2339: Property 'push' does not exist on type 'readonly number[]'.
main.ts- ファイル名
2- 行番号
9- 列番号 — この位置でtscが検査に失敗しました
TS2339- エラーコード — 検索するとTypeScriptの解説が見つかります
Property 'push' does not exist on type 'readonly number[]'.- 内容 — 期待していたもの、または受け付けられなかったもの
このエラーが出る典型パターン
パターン1
1 function printAll(items: readonly number[]): void { 2 items.push (4); ^ 3 console.log(items); 4 } 5 printAll([1, 2, 3]);
readonly number[]にはpushのような破壊的メソッドは型として存在しません。concatのような非破壊的メソッドを使います。
直し方: push を concat にします。
パターン2
1 function clearAll(items: readonly string[]): void { 2 items.splice(0, items.length); ^ 3 console.log(items); 4 } 5 clearAll(["a", "b"]);
spliceは配列を直接書き換える破壊的メソッドです。readonly配列の型には含まれていません。
直し方: splice を slice にします。
パターン3
1 function reverseAll(items: readonly number[]): void { 2 items.reverse(); ^ 3 console.log(items); 4 } 5 reverseAll([1, 2, 3]);
reverseも元の配列を書き換える破壊的メソッドのため、readonly配列の型からは除外されています。
直し方: reverse を slice にします。
パターン4
1 function tagAll(items: readonly string[]): void { 2 items.push ("z"); ^ 3 console.log(items); 4 } 5 tagAll(["a", "b"]);
readonly string[]にはpushのような破壊的メソッドは型として存在しません。
直し方: push を concat にします。
パターン5
1 function sortAll(items: readonly number[]): void { 2 items.sort (); ^ 3 console.log(items); 4 } 5 sortAll([3, 1, 2]);
sortも元の配列を書き換える破壊的メソッドのため、readonly配列の型からは除外されています。
直し方: sort を slice にします。
よくある誤解
「readonlyは実行時に書き換えを禁止する機能」だと誤解されがちですが、実際にはコンパイル時の型チェックだけの仕組みです。破壊的メソッド自体が型定義から除外されているため、呼び出しコードの時点でエラーになります。
実務での勘所
readonly number[]という明示的な型注釈とは別に、as constという書き方でも似た効果が得られます。const arr = [1, 2, 3] as const;と書くと、readonly [1, 2, 3]という、要素数と各要素の具体的な値まで固定したタプル型が推論されます。readonly number[]が「要素の型はnumberで中身は変更不可」という緩やかな制約なのに対し、as constはさらに厳密に「値そのもの」まで型として固定する、より強い凍結です。用途に応じてどちらの凍結度が必要かを選びます。