🔖

【TypeScript】定数にはas constをつけることにした

2025/01/07に公開

こんばんはmayaです!

みなさん、as constって知ってますか?巷では「定数アサーション」と呼ばれていて、定義した変数の末尾につけるとその値をreadonlyにした上でリテラル型にしてくれる機能です。

今日の業務でコードを眺めていたら、定数(const IS_VALID = ...的な)に非常に曖昧な型付けがされているのを見つけて、より良い型定義を探る中で一つの答えに辿り着いたので、ご紹介します!

どういう状況だったか

今回の記事でいう定数ってのは、単にconstキーワードで定義された変数のことではなく、「プログラムの中で値が変わらないもの」という意味での「定数」です。

そして、今日眺めてたコードがどんなんだったかというとこんな感じ。

consts/users.ts
type UsersType = {
  [key: string]: number
}

export const USER_TYPE: UsersType = {
  NONE: 0,
  PERSONAL: 1,
  BUSINESS: 2,
}

ユーザーの種類(個人・法人・指定なし)を表す定数に対して、「キーは文字列&&値は数値にすること」という型付けをしています。

何がいけないのか

見ての通りなんですが、これだと「キーが文字列であること」と「値が数値であること」しか保証できていません。型定義がゆるすぎて、キーや値が具体的になんなのか分からなくなりそうです。

そもそも型定義を行う目的の一つは、「他の場所でその値を利用しやすいように型や要素を明示すること」だと理解してます。
そして「定数」という存在は、処理の中で要素が追加されたり値が変わることがないので、型をゆるく定義する必要はありません。

ガチガチに固めちゃっていいのです。

どうすればいいか? as constを使おう!

定数アサーションという名の通り、as constは定数を型付けするのにぴったりなアサーションです。
冒頭でも紹介しましたが以下のような効果があります。

  • その値の全ての要素をリテラル型にしてくれる
  • その値の全ての要素をreadonly(読み取り専用)にしてくれる

リテラル型にしてくれる

リテラル型というのは、特定のプリミティブ型だけを許容する型のことです。
例えばconst num: number = ...はnumber型の定義なので数値であればなんでも許容します。
一方const num: 123 = ...は123という数値しか許容しないプリミティブ型の変数です。

as constを使うと、プリミティブ型はもちろん、配列やオブジェクトをそのままリテラル型にしてくれて便利です。

/** 配列の場合 */
const array1 = [1, 2, 3] // array1: number[]
const array1 = [1, 2, 3] as const //array1:[1, 2, 3]

/** オブジェクトの場合 */
const object1 = { name: "take", age: 11 }
// object1: { name: string, age: number }

const object1 = { name: "take", age: 11 } as const
// object: { name: "take", age: 11 }

readonlyにしてくれる

const object1 = { name: "take", age: 11 } as const

object1.name = "miti" // エラー
object1.age = 99 // エラー

オブジェクトの全ての要素を読み取り専用にしてくれます。
定数は処理の中で値が更新されるような使い方をされるべきではないので、誤った使用を防ぐ意味でも、as constを使ってreadonlyにしておくと良いです。

リテラル型にすると何が便利か

例えば、ユーザータイプ(指定なし・個人・法人)で一覧を検索するような処理を作るとします。
検索処理を行う関数の引数に値(0・1・2)のいずれかを渡すので、引数には0 | 1 | 2という型付けをすると良さそうです。

search.ts
// こういう状態
export function search({ type }: { type: 0 | 1 | 2 }}) {
....
}

ただ、ハードコーディングするのはスマートじゃないので、どうせなら定数から型を作れると便利ですね。as constを使うとそれが簡単にできます。

const USER_TYPE = {
  NONE: 0,
  PERSONAL: 1,
  BUSINESS: 2,
} as const

type UserTypeValue = typeof USER_TYPE[keyof typeof USER_TYPE]

export function search({ type }: { type: UserTypeValue }}) {
  ....
}

以上、今日のTipsでした〜
読んでくださってありがとうございます!

Discussion