TypeScriptのconstやas constの挙動について

概要

TypeScriptには定数を宣言する仕組みとして、constやas constが存在します。
ただ、オブジェクトや配列をconstで定義したとしても、中の値は自由に書き換えれるなどの注意点があります。
これを例とともに見ていきたいと思います。
動作環境: TS5.4.3

const

constは定数を宣言できますが、配列やオブジェクトで扱うときに注意が必要です。 数値や文字列をconstで定義して、後から置き換えようとするともちろん怒られます。

const num = 0
num = 1 // Cannot assign to 'num' because it is a constant.(2588)

一方でオブジェクトの値の置き換えは通ってしまいます。

const obj = {test: 1}
obj.test = 2 // 通る

これは、一度宣言したobj自体をまるっと書き換えているのではなく、objの存在はそのままで中のプロパティのみ書き換えているからです。
配列の場合もconstで宣言しても内部の要素は書き換え可能です。

const arr = [1, 2, 3]
arr[0] = 5

なお、オブジェクト自体を書き換えようとすると怒られます

const obj = {test: 1}
obj = {} // Cannot assign to 'obj' because it is a constant.(2588)

constの中身をreadonlyにするとどうなるか

内部のプロパティを書き換えられないように、readonlyで試しに書いてみます。 この場合、プロパティを書き換えることはできなくなります。

type place = {
    readonly value: number
    readonly name: string
}

const places: place = {
  value: 0,
  name: 'コンビニ',
}
places.name = 'スーパー' // Cannot assign to 'name' because it is a read-only property.(2540)

一方で、以下のようなreadonlyオブジェクトを持つ配列の場合は、配列操作で実質的に値を置き換えれるので注意が必要です。

type place = {
    readonly value: number
    readonly name: string
}

const places: place[] = [
    {
        value: 0,
        name: 'コンビニ',
    },
]

// 配列の要素を削除
places.pop()

// 新しい要素を入れる
places.push({
    value: 1,
    name: '学校',
});

console.log(places);
// [LOG]: [{
//   "value": 1,
//   "name": "学校"
// }] 

as const

as constは配列やオブジェクト全体をreadonlyのリテラルとして扱います。
よって内部プロパティを書き換えることはできません。

const obj = {test: 1} as const
obj.test = 2 // Cannot assign to 'test' because it is a read-only property.(2540)


const arr = [1, 2, 3] as const
arr[0] = 5 // Cannot assign to '0' because it is a read-only property.(2540)

arr.pop() // Property 'pop' does not exist on type 'readonly [1, 2, 3]'.(2339)