かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

TypeScript Readonly と as const

TypeScript なんもわからん…
Readonly と as const がよく判らなかったから調べたメモ

Readonly

TypeScriptの型システムでは、インターフェース上の個々のプロパティをreadonlyとしてマークすることができます。これにより、関数的に作業することができます(予期しない変更は良くありません)。
cf. Readonly - TypeScript Deep Dive 日本語版

オブジェクトのプロパティを変更不可にするイメージ?

type Idol = Readonly<{
  name: string;
  type: 'cute' | 'cool' | 'pop' | 'sexy';
  school?: string;
  brand?: string;
  songs: {
      [key: string]: string[];
  };
}>;

const Hoshimiya: Idol = {
  name: '星宮いちご',
  type: 'cute',
  school: 'スターライト学園',
  brand: 'Angely Sugar',
  songs: {
      cute: ['アイドル活動!'],
      cool: ['Signalize!']
  },
};

Hoshimiya.type = 'cool';
// => Cannot assign to 'type' because it is a read-only property.

Hoshimiya.songs.cute.push('輝きのエチュード');
// 変更できる

readonly な型が与えられてるオブジェクト直下のプロパティは変更しようとするとエラーになるけど、Objectや配列の中身は変更できるっぽい。

as const ( const assertion )

const Hoshimiya = {
  name: '星宮いちご',
  type: 'cute',
  school: 'スターライト学園',
  brand: 'Angely Sugar',
  songs: {
      cute: ['アイドル活動!'],
      cool: ['Signalize!']
  },
} as const;

Hoshimiya.type = 'cool';
// => Cannot assign to 'type' because it is a read-only property.

Hoshimiya.songs.cute.push('輝きのエチュード');
// => Property 'push' does not exist on type 'readonly ["アイドル活動!"]'.

// スプレッド演算子で展開して別オブジェクトするのはOK
{...Hoshimiya, name: '霧矢あおい', type: 'cool'};
//=> {name: '霧矢あおい', type: 'cool', ...};

as const だと、ネストしているオブジェクト内にも readonly が与えられて変更を加えようとしてもエラーになるっぽい。
(この構造だと引退したアイドルにしか使えなさそう…)

学校も曲も変更できないとか引退したアイドル図鑑作るとかでしか使えなさそうな型なので、こんな感じ?

type Idol = {
  readonly name: string;
  type: 'cute' | 'cool' | 'pop' | 'sexy';
  school?: string;
  brand?: string;
  songs?: {
      [key: string]: string[];
  };
}

Readonly でも as const でも別のオブジェクトに代入して変更することができてしまう

type IdolBase = Readonly<{
  name: string;
}>;

const Hoshimiya: IdolBase = {
  name: '星宮いちご',
};

const Shibuki = Hoshimiya;
// 代入はエラーにならない
Shibuki.name = '紫吹蘭';
// => Cannot assign to 'name' because it is a read-only property.

const Kiriya: { name: string } = Hoshimiya;
Kiriya.name = '霧矢あおい';
// 別途型を指定していると変更できてしまう
console.log(Hoshimiya);
// => { "name": "霧矢あおい" }

const Otome: any = Hoshimiya;
// any 型に代入すると何でも変更できる
Otome.name = '有栖川おとめ';
Otome.unit = 'ぽわプリ〜'

console.log(Hoshimiya);
// => { "name": "有栖川おとめ", "unit": "ぽわプリ〜" } 
console.log(Hoshimiya === Kiriya)
// true

Object.freeze を使うと実行時にエラーにすることができる

const Hoshimiya: IdolBase = Object.freeze({
  name: '星宮いちご',
});

const Kiriya: { name: string } = Hoshimiya;
Kiriya.name = '霧矢あおい';
// => TypeError: Cannot assign to read only property 'name' of object '#<Object>'

感想

ざっくり Readonly も as const もオブジェクトの変更をエラーで教えてくれるようにするもので、as const はネストしている配列やオブジェクトにも再帰的に readonly を指定してくれる。
ただし、オブジェクトを型が異なる別のオブジェクトに代入してしまうと、参照元のオブジェクトのプロパティを変更できてしまうので注意が必要。
関数型的な考え方をしていればオブジェクトをそのまま他のオブジェクトに代入する事自体がキモチワルイ感じだけど、防げないのでガチガチに固めるなら Object.freeze を使うのが良さそう。


[参考]

プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発

プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発

  • 作者:Boris Cherny
  • 発売日: 2020/03/16
  • メディア: 単行本(ソフトカバー)

毎週火曜日アイカツ!をみましょう!!!