TypeScript | 基礎文法:オブジェクト基礎 – readonlyプロパティ

TypeScript
スポンサーリンク

readonlyプロパティとは何か(「一度決めたら変えない」約束)

readonly プロパティは、「このプロパティは一度値を入れたら、あとから書き換えてはいけない」という約束を型レベルで表す仕組みです。
TypeScriptでは、オブジェクト型のプロパティに readonly を付けることで、そのプロパティを「読み取り専用」にできます。

type User = {
  readonly id: number;
  name: string;
};

const user: User = {
  id: 1,
  name: "Taro",
};

user.name = "Jiro"; // OK
user.id = 2;        // エラー: Cannot assign to 'id' because it is a read-only property
TypeScript

ここで id は「一度決めたら変えない値」として守られます。
実行時にロックされるわけではなく、コンパイル時に「変えようとしているコード」を止めてくれるのがポイントです。


readonlyプロパティの基本的な書き方

type でのreadonlyプロパティ

type Config = {
  readonly env: string;
  readonly debug: boolean;
};

const config: Config = {
  env: "dev",
  debug: true,
};

// config.env = "prod"; // エラー
TypeScript

readonly env: string は、「env プロパティは string だが、再代入はできない」という意味です。
debug も同様に、一度決めたら変えられない設定値として扱われます。

interface でのreadonlyプロパティ

interface User {
  readonly id: number;
  name: string;
}

const user: User = {
  id: 1,
  name: "Taro",
};

user.name = "Jiro"; // OK
// user.id = 2;     // エラー
TypeScript

typeinterface のどちらでも、readonly の書き方と意味は同じです。
「このプロパティは変えちゃダメ」という意図を、型としてはっきり宣言できます。


なぜreadonlyを付けるのか(メリットのイメージ)

「変えてはいけないもの」をコードで守る

ID、作成日時、設定値など、「一度決まったら変わるべきではない値」はたくさんあります。
readonly を付けておくと、うっかり書き換えるコードを書いたときに、コンパイル時に止めてくれます。

interface User {
  readonly id: number;
  name: string;
}

function rename(user: User, newName: string) {
  user.name = newName; // OK
  // user.id = 999;    // エラー(IDを変えようとしている)
}
TypeScript

「この関数は名前だけ変えるつもりだったのに、IDまで変えてしまった」
そんなバグを、型が先回りして防いでくれるイメージです。

「この関数はオブジェクトを書き換えません」を型で表現できる

type FooBarBaz = {
  foo: number;
  bar: number;
  baz: number;
};

function sum(obj: Readonly<FooBarBaz>) {
  return obj.foo + obj.bar + obj.baz;
}
TypeScript

Readonly<FooBarBaz> を使うと、「この関数は obj の中身を変更しない」ということを型で宣言できます。
呼び出す側も、「この関数に渡してもオブジェクトは壊されない」と安心して使えます。


readonlyは「そのプロパティだけ」に効く(再帰的ではない)

ネストしたオブジェクトの挙動

readonly は、指定したプロパティにだけ効きます
中にさらにオブジェクトが入っている場合、その中身までは自動では readonly になりません。

type Outer = {
  readonly inner: {
    value: number;
  };
};

let obj: Outer = {
  inner: { value: 1 },
};

// obj.inner = { value: 2 }; // エラー: inner は readonly
obj.inner.value = 2;          // これはOK
TypeScript

ここでは、

  • inner プロパティ自体は readonly(別のオブジェクトを代入し直すのはNG)
  • しかし inner.value は readonly ではないので、書き換え可能

という状態です。

「中身も全部 readonly にしたい」場合は、Readonly<T> や再帰的なユーティリティ型を使う必要がありますが、
まずは「readonly は“そのプロパティ1段分”だけに効く」と覚えておけば十分です。


readonly配列との関係(さらっと触れておく)

配列にもreadonlyは使える

const list: readonly number[] = [1, 2, 3];

// list.push(4);   // エラー: push が存在しない
// list[0] = 9;    // エラー: インデックス経由の代入も不可
TypeScript

readonly number[] は、「この配列は中身を変更してはいけない」という意味です。
pushpop のような破壊的メソッドは使えなくなり、要素の再代入も禁止されます。

ただし、mapfilter のように「新しい配列を返すメソッド」は問題なく使えます。
「元の配列を壊さない」という設計を、型で強制できるわけです。


初心者がまず掴んでおきたいreadonlyプロパティの感覚

readonlyプロパティの本質は、こうです。

「この値は、作ったときに決めて、それ以降は変えない」という約束を、型として宣言する。

その結果として、

  • IDや設定値など「変わるべきでないもの」を守れる
  • 関数が「引数のオブジェクトを書き換えるかどうか」を型で表現できる
  • うっかりした代入ミスをコンパイル時に潰せる

という状態になります。

「このプロパティ、本当にあとから変わっていい?」
そう自分に問いかけて、「変わってほしくない」と思ったら readonly を付ける。

その小さな判断の積み重ねが、壊れにくくて意図が伝わるコードを作っていきます。

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