TypeScriptでReadonlyを使って変更禁止の型を作る方法!初心者向け完全解説
生徒
「TypeScriptで、一度決めたデータを後から書き換えられないように守る方法はありますか?」
先生
「TypeScriptには、Readonlyという便利な機能があります。これを使うと、大切なデータを間違えて書き換えてしまうミスを防ぐことができるんですよ。」
生徒
「後から変更できないようにロックをかけるようなイメージでしょうか?具体的にどうやって使うのか教えてください!」
先生
「その通りです!それでは、初心者の方でも分かりやすく、基本的な使い方から順番に解説していきましょう!」
1. Readonlyとは何かを学ぼう
TypeScriptのReadonly(リードオンリー)とは、日本語に直訳すると「読み取り専用」という意味になります。プログラミングの世界における読み取り専用とは、「中身を見ることはできるけれど、中身を書き換えることはできない」という状態を指します。
例えば、皆さんの手元にある「教科書」を想像してみてください。教科書に書かれている文字は読むことができますが、勝手に内容を書き換えることはできませんよね。このように、プログラムの中でも「このデータは絶対に書き換えられたくない!」という時に、Readonlyという魔法をかけてデータを保護します。
なぜこのような機能が必要なのでしょうか。それは、人間がプログラミングをしていると、ついうっかり大切なデータを上書きしてしまうミスが起こるからです。Readonlyを使ってあらかじめ制限をかけておけば、もし間違えて書き換えようとした時に、TypeScriptが「それは変更できませんよ!」とエラーを出して教えてくれます。これにより、プログラムの安全性がぐっと高まるのです。
2. 型のユーティリティという便利な仕組み
TypeScriptには、既存の型を元にして、新しい性質を持った型を簡単に作り出す「ユーティリティ型(Utility Types)」という便利な仕組みが用意されています。今回学習するReadonlyも、このユーティリティ型の一種です。
プログラミング未経験の方にとって「型」という言葉は少し難しく感じるかもしれませんが、これはデータの「設計図」や「入れ物のルール」だと考えてください。例えば、「名前は文字で入れる」「年齢は数字で入れる」といったルールを決めるのが型です。
ユーティリティ型を使うと、元の設計図を書き換えることなく、「この設計図の項目をすべて読み取り専用にする」といったカスタマイズが瞬時に行えます。これにより、同じような設計図を何度も手書きで作り直す手間が省け、コードがすっきりと綺麗になります。Readonly<T>の<T>の部分には、元となる型を当てはめて使います。
3. Readonlyを使ってデータをロックする基本コード
それでは、実際にReadonlyを使ってみましょう。まずは、普通の型(変更可能な状態)と、Readonlyを適用した型(変更禁止の状態)を比較してみます。
以下のコード例では、ユーザーの情報を扱う設計図を作っています。どのような違いが出るのか注目してください。
// ユーザーの設計図(型)を作る
type User = {
name: string;
age: number;
};
// Readonlyを使って、すべての項目を変更禁止にした新しい型を作る
type ReadonlyUser = Readonly<User>;
// データを実際に作成する
const myUser: ReadonlyUser = {
name: "太郎",
age: 25
};
// データを書き換えようとしてみる(ここでエラーが発生します!)
// myUser.name = "次郎";
このコードを実行しようとすると、TypeScriptは「nameは読み取り専用プロパティなので、代入することはできません」というメッセージを出して止めてくれます。もしReadonlyを使っていなければ、名前は「次郎」に書き換わってしまいます。このように、意図しないデータの変更を未然に防ぐことができるのが、この機能の最大のメリットです。
4. Mapped Typesの仕組みをやさしく解説
Readonlyがなぜこれほど便利に動くのか、その裏側にある「Mapped Types(マップドタイプス)」という技術についても触れておきましょう。これは、中級者以上の内容に聞こえるかもしれませんが、考え方はとてもシンプルです。
「マップ(Map)」とは、あるものとあるものを対応させるという意味があります。Mapped Typesは、元の型にある複数の項目(プロパティ)を一つずつ取り出して、それらに対して一括で「読み取り専用にする」などの加工を施す機能です。
例えば、お弁当箱の仕切り一つひとつに「触るな!」というシールを貼っていく作業を自動化してくれるようなものです。お弁当の中身が「おにぎり」でも「卵焼き」でも、自動的にすべての具材に対してシールを貼ってくれるため、中身が増えても手間がかかりません。ReadonlyはこのMapped Typesの仕組みを使って、私たちの代わりにすべての項目を保護してくれているのです。
5. 配列を読み取り専用にする方法
Readonlyは、名前や年齢といった単一のデータだけでなく、複数のデータが並んだ「配列(リスト)」に対しても使うことができます。配列を読み取り専用にすると、中身を書き換えるだけでなく、新しい要素を追加したり、削除したりすることも禁止できます。
例えば、お店のメニュー表や、一週間の曜日の名前など、決まったリストを勝手にいじられたくない場合に非常に有効です。
// 文字列が入った読み取り専用の配列を作る
const weekdays: ReadonlyArray<string> = ["月", "火", "水", "木", "金"];
// 配列の中身を書き換えようとする(エラー!)
// weekdays[0] = "日";
// 新しい要素を追加しようとする(エラー!)
// weekdays.push("土");
実行結果を確認すると、配列の操作を行うための命令(pushなど)自体が使えなくなっていることが分かります。このように、ReadonlyArrayを使うことで、リスト全体の整合性を保つことができます。パソコンの操作に慣れていない方が間違えてデータを消してしまうような事故も、プログラム側でガードできるのです。
6. ネストされた構造への注意点
ここで一つ、少し注意が必要な点をお話しします。それは「ネスト(入れ子)構造」になっているデータについてです。ネストとは、箱の中にさらに箱が入っているような状態を指します。
実は、標準のReadonlyは、一番外側の箱にだけロックをかけます。もし箱の中にさらに別のオブジェクトが入っている場合、その中のデータまでは守ってくれないという特徴があります。これは「浅い(Shallow)」保護と呼ばれます。
type Address = {
city: string;
};
type Person = {
name: string;
info: Address; // ここが入れ子(ネスト)
};
const taro: Readonly<Person> = {
name: "太郎",
info: { city: "東京" }
};
// taro.name = "花子"; // これはエラー(守られている)
taro.info.city = "大阪"; // これはエラーにならない(中の箱は守られていない)
このように、複雑なデータ構造を扱うときは、中のデータまでしっかり守られているかを確認する必要があります。プログラミングに慣れてきたら、この「中身まで全部守る」ための応用テクニックも学んでいくことになりますが、まずは「基本は外側を守るもの」と覚えておけば大丈夫です。
7. 実際の開発でReadonlyが活躍する場面
実際に、どのような場面でこの機能が使われているのでしょうか。最も多いのは、「設定値(コンフィグ)」の管理です。
アプリケーションを動かすための基本的な設定、例えば「サイトの名前」や「サーバーの接続先」などは、プログラムが動いている途中で変わってしまうと困ります。こうした重要な情報をReadonlyで定義しておくことで、安全なシステムを構築できます。
また、最近のプログラミング手法では「一度作ったデータは変えずに、変えたいときは新しいデータを作る」という考え方(イミュータブルといいます)が推奨されています。この考え方を実践する上でも、Readonlyは欠かせないツールとなっています。初心者の方も、最初から「大切なものは守る」という意識を持って書くことで、プロのような質の高いコードが書けるようになりますよ。
8. エラーを恐れずに使いこなそう
プログラミングを始めたばかりの頃は、赤い波線で表示される「エラー」を見ると、自分が何か悪いことをしてしまったのではないかと不安になるかもしれません。しかし、TypeScriptのエラーは、あなたを助けてくれる「親切なアドバイス」です。
特にReadonlyによるエラーは、「ここでデータが書き換わると後で困るかもしれませんよ」という警告です。この警告を無視せずに、なぜエラーが出ているのかを考えることが、プログラミング上達の近道になります。自分でルールを作り、そのルールに従って正しくプログラムが動く。この心地よさをぜひ体験してみてください。
// ゲームのキャラクターのステータスを守る例
type Character = {
readonly id: number; // 項目ごとにreadonlyをつけることも可能
name: string;
level: number;
};
const hero: Character = {
id: 1,
name: "勇者",
level: 1
};
// hero.id = 999; // IDは絶対に変わらないようにロックされているのでエラー!
hero.name = "伝説の勇者"; // 名前は変更可能
このように、全体をまとめて守る方法(Readonlyユーティリティ)と、特定の項目だけをピンポイントで守る方法(readonlyキーワード)を使い分けることもできます。場面に合わせて最適な守り方を選んでいきましょう!
9. 学習のステップアップに向けて
今回は、TypeScriptのReadonlyについて、その基本的な使い方とメリットを解説しました。一見地味な機能に見えるかもしれませんが、大規模なシステムになればなるほど、こうした「データの保護」が非常に重要になってきます。
パソコンを触ったことがない方でも、まずは「型=設計図」「Readonly=書き換え禁止シール」というイメージを持つだけで、立派な第一歩を踏み出したことになります。次は、自分でオリジナルの設計図を作り、そこにReadonlyを適用して、わざとエラーを出して遊んでみてください。エラーを味方につけることができれば、プログラミングはもっと楽しくなります。
TypeScriptには、他にも便利なユーティリティ型がたくさんあります。一つひとつは小さな道具ですが、それらを組み合わせることで、どんなに複雑なアプリケーションでも安全に作り上げることができるようになります。一歩ずつ、楽しみながら学んでいきましょう!