【入門編】 ReadonlyArrayによる配列の不変性 – TypeScript実践ガイド

こんにちは!TypeScriptの世界へようこそ。チーフアーキテクトの私です。

フロントエンドの現場に立っていると、「うわっ、気づかないうちに配列の中身が書き換わってバグってる……!」なんて修羅場に、多かれ少なかれ誰もが直面したことがあるはずです。プログラムが大きくなればなるほど、どこで誰がデータ書き換えたのか犯人探しをするのは本当に骨が折れますよね。

そこで今回ご紹介するのが、配列の平和を守る強力な盾、`ReadonlyArray` です!

「なんだか名前が難しそう……」「英語が出てきただけでアレルギーが出るよ」なんて思った方も、どうか安心してください。今回は、身近な例えを交えながら、優しくゆっくり紐解いていきますよ。

—

そもそも、ふつうの配列ってそんなに危ないの?

まずは、普段私たちが何気なく使っている「ふつうの配列」の姿を見てみましょう。TypeScriptの世界では、配列は次のように書きますよね。

// お馴染みの文字列の配列
const fruits: string[] = [‘りんご’, ‘みかん’, ‘バナナ’];

この `fruits` という配列、実はめちゃくちゃ「お人好し」で「流されやすい」性格をしています。誰かに「ねぇ、みかんをドリアンに変えてよ」って頼まれたら、嫌と言えずにその場で自分自身を変形(ミューテーション)させてしまうんです。

const fruits: string[] = [‘りんご’, ‘みかん’, ‘バナナ’];

// うっかり誰かが配列の中身を書き換えちゃった!
fruits[1] = ‘ドリアン’;

console.log(fruits); // [‘りんご’, ‘ドリアン’, ‘バナナ’] ―― ええっ!?

これ、個人開発ならまだしも、チーム開発で何人ものプログラマーが触るコードだったら大惨事になりかねません。「え、さっきまでみかんだったのに、なんで急にドリアンになったの!?」という犯人探しの夜が始まってしまいます。

—

例え話:美術館の「触っちゃダメ!」な展示品

ここでちょっと、身近なシーンを想像してみてください。

あなたは今、美術館の展示室に来ています。
ガラスケースの中に、美味しそうな(?)りんごの彫刻が飾られています。

  • ふつうの配列 (`string[]`):

野外に置いてある粘土細工のようなものです。誰でも自由に近づいて、勝手に形を変えたり、別のものに作り変えたりできちゃいます。

  • `ReadonlyArray`:

頑丈なガラスケースの中に鎮座する「お手を触れないでください」と書かれた美術品です。

見ることはできるし、何が置いてあるか数えることもできる。でも、あなたの手で勝手に形を変えることは絶対に許されません。

TypeScriptの `ReadonlyArray` は、まさにこの「ガラスケース」の役割を果たしてくれます。「この配列は誰も中身をいじっちゃダメだからね!」と、TypeScriptの编译器(コンパイラ)が厳しく見張ってくれるようになるんです。

—

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ライフが、より安心で快適なものになりますように!

コメント

タイトルとURLをコピーしました