こんにちは!TypeScriptの世界へようこそ。チーフアーキテクトの私です。
フロントエンドの現場に立っていると、「うわっ、気づかないうちに配列の中身が書き換わってバグってる……!」なんて修羅場に、多かれ少なかれ誰もが直面したことがあるはずです。プログラムが大きくなればなるほど、どこで誰がデータ書き換えたのか犯人探しをするのは本当に骨が折れますよね。
そこで今回ご紹介するのが、配列の平和を守る強力な盾、`ReadonlyArray
「なんだか名前が難しそう……」「英語が出てきただけでアレルギーが出るよ」なんて思った方も、どうか安心してください。今回は、身近な例えを交えながら、優しくゆっくり紐解いていきますよ。
—
そもそも、ふつうの配列ってそんなに危ないの?
まずは、普段私たちが何気なく使っている「ふつうの配列」の姿を見てみましょう。TypeScriptの世界では、配列は次のように書きますよね。
// お馴染みの文字列の配列
const fruits: string[] = [‘りんご’, ‘みかん’, ‘バナナ’];
この `fruits` という配列、実はめちゃくちゃ「お人好し」で「流されやすい」性格をしています。誰かに「ねぇ、みかんをドリアンに変えてよ」って頼まれたら、嫌と言えずにその場で自分自身を変形(ミューテーション)させてしまうんです。
const fruits: string[] = [‘りんご’, ‘みかん’, ‘バナナ’];
// うっかり誰かが配列の中身を書き換えちゃった!
fruits[1] = ‘ドリアン’;
console.log(fruits); // [‘りんご’, ‘ドリアン’, ‘バナナ’] ―― ええっ!?
これ、個人開発ならまだしも、チーム開発で何人ものプログラマーが触るコードだったら大惨事になりかねません。「え、さっきまでみかんだったのに、なんで急にドリアンになったの!?」という犯人探しの夜が始まってしまいます。
—
例え話:美術館の「触っちゃダメ!」な展示品
ここでちょっと、身近なシーンを想像してみてください。
あなたは今、美術館の展示室に来ています。
ガラスケースの中に、美味しそうな(?)りんごの彫刻が飾られています。
- ふつうの配列 (`string[]`):
野外に置いてある粘土細工のようなものです。誰でも自由に近づいて、勝手に形を変えたり、別のものに作り変えたりできちゃいます。
- `ReadonlyArray
` :
頑丈なガラスケースの中に鎮座する「お手を触れないでください」と書かれた美術品です。
見ることはできるし、何が置いてあるか数えることもできる。でも、あなたの手で勝手に形を変えることは絶対に許されません。
TypeScriptの `ReadonlyArray
—
ReadonlyArray を使ってみよう
では、実際にどうやって書くのか見てみましょう。書き方はとってもシンプルです。型名の前に `ReadonlyArray<` をつけるか、もっとスマートな省略形を使います。
// 書き方その1:ReadonlyArrayを使う
const safeFruits: ReadonlyArray
// 書き方その2:実務でよく使われるスマートな書き方(readonlyキーワード)
const smarterFruits: readonly string[] = [‘りんご’, ‘みかん’, ‘バナナ’];
実務の現場では、タイピングの量も減るため、後者の `readonly string[]` という書き方が好まれる傾向にあります。お好みの方で大丈夫ですよ!
さて、この「触っちゃダメな配列」に対して、さっきのように中身を書き換えようとするとどうなるでしょうか?
const safeFruits: readonly string[] = [‘りんご’, ‘みかん’, ‘バナナ’];
// ❌ やってみようとする
// safeFruits[1] = ‘ドリアン’;
// 🚨 エラー発生!
// 「読み取り専用プロパティであるため、代入することはできません。」
おっ、TypeScriptの先生が怒って止めてくれましたね!「コラコラ、ここは触っちゃダメな場所だよ」と、コードを実行する前にエディタ上で教えてくれるのです。これでバグを未然に防ぐことができます。
—
「変更できない」ってことは、何もできないの?
ここで、「中身が変えられないなら、配列の意味なくない?」と思ったそこのあなた。すごく良い着眼点です!
大丈夫、安心してください。「読むこと(参照)」や「調べること」はいくらでもできます。 美術館の展示品も、じっくり鑑賞したり、写真を撮ったり、数を数えたりすることはできますよね。それと同じです。
const numbers: readonly number[] = [1, 2, 3, 4, 5];
// 1. 中身を読む(インデックスでアクセス)
console.log(numbers[0]); // 1 (これはOK!)
// 2. 長さを調べる
console.log(numbers.length); // 5 (これもOK!)
// 3. 新しい配列を作り出すメソッド(mapやfilterなど)
const doubleNumbers = numbers.map(n => n 2);
console.log(doubleNumbers); // [2, 4, 6, 8, 10] (元を壊さず新しいのを作るならOK!)
一方で、元の配列を直接破壊するようなメソッド(`.push()` や `.pop()`、`.sort()` など)を使おうとすると、これまた「そんなことしちゃダメ!」とエラーになります。
const numbers: readonly number[] = [1, 2, 3];
// ❌ 怒られる代表的な例
// numbers.push(4); // エラー:そんなメソッドないよ!
// numbers.sort(); // エラー:並び替えで中身が変わっちゃうからダメ!
「変化させない(不変性 / Immutability)」を保つことで、プログラムの動きがぐっと予測しやすくなり、予期せぬバグから解放される。これが、関数型プログラミングや近年のモダンなWeb開発(Reactなど)で `readonly` が愛されまくっている理由なんです。
—
実際のWeb制作・開発でどう活きる?
例えば、ショッピングサイトのカート機能を想像してみてください。
ユーザーが「商品をカートに入れた」「数量を変更した」というとき、うっかり元のカートデータを直接書き換えてしまうと、画面の再描画がうまく動かなかったり、思わぬところでデータがバグったりします。
そこで、関数の引数やコンポーネントのプロパティ(Props)に `readonly` をつけておきます。
interface CartItem {
id: string;
name: string;
price: number;
}
// カートの中身を表示するだけの優しいコンポーネント(イメージ)
// 「この関数の中で、勝手にカートの中身を書き換えたりしないでね」と約束する
function displayCart(items: readonly CartItem[]) {
// items.push(…) と書こうものなら即座にエラーになるので安心!
items.forEach(item => {
console.log(`${item-name}: ${item.price}円`);
});
}
「私はこのデータを読むだけです、壊しませんよ」という意思表示をコードで明確にすることで、自分自身やチームの仲間への強力なメッセージ(ドキュメント代わり)にもなるんです。
—
まとめ
- `ReadonlyArray
`(または `readonly T[]`) は、配列の要素を書き換え不可能(不変)にするための安全ガード。 - うっかり中身を書き換えてバグを埋め込むミスを、TypeScriptが事前にガッチリ防いでくれる。
- 読むことや、新しい配列を作る操作(`map` など)はこれまで通り自由に行える。
最初は「なんか窮屈だな」と感じるかもしれませんが、慣れてくると「`readonly` がないと逆に不安で夜も眠れない体」になっていきます(笑)。
まずは怖がらずに、あなたの書くコードの「変えられたくない配列」に `readonly` をちょこんと添えてみてくださいね。あなたのTypeScriptライフが、より安心で快適なものになりますように!

コメント