🙆‍♀️

[TypeScript]constとreadonlyの違いについて

2024/01/06に公開

constとreadonlyの違い

JavaScriptでは、constで宣言した変数は代入不可になります。
TypeScriptではオブジェクトの型のプロパティにreadonly修飾子をつけると、そのプロパティが代入不可になります。

constは変数への代入を禁止にするもの

constは変数への代入を禁止するもの。例えば、constで宣言されたxに値を代入しようとすると、TypeScriptではコンパイルエラーになり、JavaScriptでは実行時エラーになります。

const x = 1;
x = 2;
// Cannot assign to 'x' because it is a constant.(定数であるため、'x' に代入することはできません。

このように、変数そのものに代入をすることはできません。
以下のように、変数のプロパティへの代入は許可されています。

const x = { y: 1 };
x = { y: 2 }; // 変数そのものへの代入は不可
x.y = 2; // プロパティへの代入は許可

readonlyはプロパティへの代入を禁止にするもの

TypeScriptのreadonlyはプロパティへの代入を禁止するものです。
例えば、readonlyがついたプロパティxに値を代入しようとすると、コンパイルエラーになります。

let obj: { readonly x: number } = { x: 1 };
obj = { x: 2 };
obj.x = 2;
// プロパティxに値を代入しようとしているためエラーとなる。

const assertionについて

TypeScriptにおいて const assertion は、変数やプロパティに対して型を推論せず、明示的にその変数やプロパティが不変であることを宣言するための機能です。
通常、TypeScriptは変数の初期化時にその型を推論しますが、const assertion を使うとその変数が後で変更されないことをコンパイラに伝えることができます。

// 通常の型推論
let x = 10; // TypeScriptはここでxの型をnumberと推論

// const assertionを使用
let y = 10 as const; // yは型推論を阻止し、型は10リテラル型になる

// プロパティに対するconst assertion
const obj = {
  a: 1,
  b: "hello",
} as const;

// obj.aの型は1リテラル型、obj.bの型は"hello"リテラル型

この例では、as const を使って変数 y およびオブジェクト obj のプロパティに const assertion を適用しています。これにより、変数やプロパティが後で変更されないことを示し、TypeScriptはそれに基づいて型を厳密に制約します。

次は、以下の例です。

type Country = {
    name: string;
    capitalCity: string;
};
   
type Continent = {
    readonly name: string;
    readonly canada: Country;
    readonly us: Country;
    readonly mexico: Country;
};

const america: Continent = {
    name: "North American Continent",
    canada: {
      name: "Republic of Canada",
      capitalCity: "Ottawa",
    },
    us: {
      name: "United States of America",
      capitalCity: "Washington, D.C.",
    },
    mexico: {
      name: "United Mexican States",
      capitalCity: "Mexico City",
    },
};

このサンプルコードの「Continent」のタイプエイリアスがもつプロパティは、readinlyとしているため、以下のように代入することはできません。

america.name = "African Continent";
// 読み取り専用プロパティであるため、'name' に代入することはできません。

america.canada = {
  name: "Republic of Côte d'Ivoire",
  capitalCity: "Yamoussoukro",
};
// 読み取り専用プロパティであるため、'canada' に代入することはできません。

しかし、次のようなことはできます。

america.canada.name = "Republic of Côte d'Ivoire";
america.canada.capitalCity = "Yamoussoukro";

これはreadonlyをつけたプロパティ(canada, us, mexico)がオブジェクトである場合、そのオブジェクトのプロパティ(例えば、canadaオブジェクトのnameとcapitalCity)までは、readonlyにしないという仕組みとなっているためです。

const assertionはすべてのプロパティを固定する

先ほどのコードにas constを付け加えます。

const america = {
  name: "North American Continent",
  canada: {
    name: "Republic of Canada",
    capitalCity: "Ottawa",
  },
  us: {
    name: "United States of America",
    capitalCity: "Washington, D.C.",
  },
  mexico: {
    name: "United Mexican States",
    capitalCity: "Mexico City",
  },
} as const;

こうすることによって、以下のように、as constをつけたオブジェクト(america)の中身のオブジェクト(canada, us, mexico)もreadonlyとして、代入をできなくすることができます。

america.name = "African Continent";
// 読み取り専用プロパティであるため、'name' に代入することはできません。

america.canada = {
  name: "Republic of Côte d'Ivoire",
  capitalCity: "Yamoussoukro",
};
// 読み取り専用プロパティであるため、'canada' に代入することはできません。
// 
america.canada.name = "Republic of Côte d'Ivoire";
// 読み取り専用プロパティであるため、'name' に代入することはできません。

america.canada.capitalCity = "Yamoussoukro";
// 読み取り専用プロパティであるため、'canada' に代入することはできません。

このように、const assertionを使用することで、より堅牢なオブジェクトを作ることができます。

参考

https://typescriptbook.jp/

Discussion