📖

TypeScriptのreadonlyを知ろう

に公開

概要

別記事を書くにあたりreadonlyを理解しようの会

readonlyとは

読み取り専用を示す型
参考: https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#readonly-and-const

constを用いた定数宣言で

const hoge = "hoge";
hoge = "fuga";

をすると 定数であるため、'hoge' に代入することはできません。 エラーが発生するが

const hoge = [1, 2, 3];
hoge = [1, 2]; // 🚫これはできない
hoge.push(4); // ✅プロパティの追加は可能

const fuga = { name: "ふが太郎", message: "こんにちは、ふが太郎です。" };
fuga = { name: "ふが次郎", message: "こんにちは、ふが次郎です。" }; // 🚫これはできない
fuga.name = "ふが次郎"; // ✅プロパティの変更は可能

などはできるようになっている。
これはJavaScriptのconstの仕様で

定数がオブジェクトであった場合、そのプロパティを追加したり、更新したり、削除したりすることができます。

となっているため。これをreadonlyを使ってできなくする。

type Hoge = readonly number[];
const hoge: Hoge = [1, 2, 3];
hoge.push(4);

type Fuga = { readonly name: string; readonly message: string };
const fuga: Fuga = { name: "ふが太郎", message: "こんにちは、ふが太郎です。" };
fuga.name = "ふが次郎";

とするとhoge.push()は プロパティ 'push' は型 'Hoge' に存在しません。 エラー、fuga.nameは 読み取り専用プロパティであるため、'name' に代入することはできません。 エラーになる。

したがって、readonlyを付けることで意図しない上書きを防ぐことができる

readonlyをつけられるもの

readonlyは配列やタプル、オブジェクトのプロパティに使用できる。

type A = readonly string[]; // ✅配列型なのでOK
type B = readonly [string, number, boolean]; // ✅タプルの型なのでOK
type C = { readonly name: string }; // ✅オブジェクトのプロパティなのでOK
type D = readonly { name: string }; // 🚫オブジェクト自体にはNG
type E = readonly string; // 🚫配列やタプルやオブジェクトじゃないのでNG
type F = readonly 0 | 1 | boolean; // 🚫配列やタプルやオブジェクトじゃないのでNG

readonlyがあるものも代入できる

readonlyとして若干のつまづきポイントだったのでメモ。
以下のようにreadonly string[]string[]を使って定義することができる。

type Hoge = readonly string[];

const array: string[] = ["ほげ", "ふが"];
const hoge: Hoge = array;

readonlyは型の定義というよりかは定義した値の扱い方法を示すものなので、
readonlyの後ろの型に当てはまるのであればエラーにならない。

type Hoge = readonly string[];

const array: string[] = ["ほげ", "ふが"];
const hoge: Hoge = array;

あくまでもreadonlyを追記したら追加や変更ができないという理解をしておけばOK。

type Hoge = readonly string[];

const array: string[] = ["ほげ", "ふが"];
const hoge: Hoge = array;

hoge.push("ぴよ"); // 🚫readonlyなのでエラー

ちなみに

ネストがあるオブジェクトに対して

type Fuga = {
  readonly name: string;
  readonly message: string;
  readonly birthday: { year: number; month: number; day: number };
};
const fuga: Fuga = {
  name: "ふが太郎",
  message: "こんにちは、ふが太郎です。",
  birthday: { year: 2001, month: 1, day: 1 },
};

とした場合、fuga.birthdayの変更はできないがfuga.birthday.yearの変更はできてしまうため

type Fuga = {
  readonly name: string;
  readonly message: string;
  readonly birthday: {
    readonly year: number;
    readonly month: number;
    readonly day: number;
  };
};

とする必要がある。

が、プロパティが増える度にreadonlyを付けて対応しなきゃいけなくて大変そうなので

const fuga = {
  name: "ふが太郎",
  message: "こんにちは、ふが太郎です。",
  birthday: { year: 2001, month: 1, day: 1 },
} as const;

as const を付けて手っ取り早く指定することが多いかも。

Discussion