TypeScript

【TypeScript】const assertionとreadonlyの違い

マナビト
マナビト
こんにちはマナビトです。
普段はフロントエンドエンジニアとしてJavaScript/TypeScript/Aangular/GraphQLを
メインに開発業務を行なっています。

今回は久々にTypeScriptをテーマにした記事を書いていきたいと思います。「const assertion」を結構使用してますが、readonlyとどう違うのか疑問に思ったので調べてみました。

const assertion(as const)とは

const assertionとは全ての値を読み取り専用及び変更不可にするアサーションです。記載方法はオブジェクトの末尾にas constを記述すことで、プロパティをreadonlyで指定した場合と同等の扱いにすることができます。

const sampleObject = {
   a: 'Foo', 
   b: 'Apple',
   c: 42,
   d: 50 
} as const 

sampleObject.a = 'Bar' // Cannot assign to 'a' because it is a read-only property.

as constをオブジェクトに指定した場合、オブジェクトに別の値を代入すると、エラーが発生して怒られる為、代入することはできません。もちろんas constを記載しない場合は、通常通り値を代入することが出来ます。

const sampleObject = {
   a: 'Foo', 
   b: 'Apple',
   c: 42,
   d: 50 
} as const 

sampleObject.a = 'Bar'

またas constの他の特徴として、Literal TypesがWideningされないこともあげられます。

Wideningは型が自動で拡張するTypeScriptの機能のことを指します。
constやletで宣言したLiteralTypesの変数に、オブジェクトのプロパティとした時にWideningが発生して予期せぬ挙動が引き起こされてしまいます。

例えば、message変数にHello worldを宣言したとします。

const message = 'hello world';

messageをオブジェクトのプロパティとして代入した場合、型が自動で拡張して値が変わってしまいます。

const message = 'hello world';

const test = { message };

console.log(test) // hello world
test.message = 'Second world';

console.log(test.message); // Second world

as constを付け加えることで、このLiteral TypesがWideningされるのを防ぐことができます。

const message = 'hello world' as const;

const test = { 
  message 
  };

test.message = 'Second world'; // Type '"Second world"' is not assignable to type '"hello world"'.

「readonly」と「const assertion」の違い

「readonly」と「const assertion」は記載することで両方とも、値を変更することが出来なくなる特徴があります。

主な違いとしては、const assertionはオプジェクト全体に宣言する事になり、全てのプロパティが対象なりますが、readonlyは必要なプロパティのみつけることが出来るということです。

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

readonlyが記載されたプロパティがオブジェクトまでである為、そのオブジェクトのプロパティにまでreadonlyにすることはできません。

type Country = {
  name: string;
  capitalCity: string;
};

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

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

上記の通り記載した場合、そのオブジェクトのプロパティまでreadonlyが起因せず、値を代入することができます。

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

一方で、const assertionの場合は再起的にreadonlyとすることが出来るため、例えば以下のように値を代入することが出来ません。

america.name = "African Continent";
// Cannot assign to 'name' because it is a read-only property.
america.canada = {
  name: "Republic of Côte d'Ivoire",
  capitalCity: "Yamoussoukro",
};
// Cannot assign to 'canada' because it is a read-only property.

また先ほど記述した、Literal TypesのがWideningされるのを防ぐのもconst assertionとreadonlyの違いとなります。

本日はここまでとなります。
最後までお読み下さりありがとうございました。

■ 参考文献