TypeScript as constで型の拡大を防ぐ方法

問題

定数オブジェクトを定義しても、TypeScriptが型を広く推論してしまいます。

const STATUS = {
  PENDING: 'pending',
  ACTIVE: 'active',
  CLOSED: 'closed',
};
// 型: { PENDING: string, ACTIVE: string, CLOSED: string }
// 'pending' | 'active' | 'closed'ではなく、ただのstring

STATUS.PENDINGstring型になるため、'pending' | 'active' | 'closed'を期待する関数パラメータで型エラーが発生します。

解決方法

as constを付けるだけです。

const STATUS = {
  PENDING: 'pending',
  ACTIVE: 'active',
  CLOSED: 'closed',
} as const;
// 型: { readonly PENDING: 'pending', readonly ACTIVE: 'active', readonly CLOSED: 'closed' }

これでSTATUS.PENDINGstringではなく'pending'リテラル型になります。ユニオン型も抽出できます:

type StatusType = typeof STATUS[keyof typeof STATUS];
// 'pending' | 'active' | 'closed'

function updateStatus(status: StatusType) {
  // statusは正確に3つの値のいずれかのみ受け付けます
}

updateStatus(STATUS.ACTIVE);  // OK
updateStatus('random');        // コンパイルエラー

配列にも使えます:

const ROLES = ['admin', 'editor', 'viewer'] as const;
type Role = typeof ROLES[number];  // 'admin' | 'editor' | 'viewer'

// string[]ではなくタプルとして推論されます
// readonly ['admin', 'editor', 'viewer']

関数の戻り値にも便利です:

function getConfig() {
  return {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    retries: 3,
  } as const;
}
// retriesの型はnumberではなく3

ポイント

  • as constはすべてのプロパティをreadonly+リテラル型にして、型の拡大を防ぎます
  • typeof + keyofでユニオン型を抽出すれば、enumなしで同じ効果が得られます
  • ランタイムオーバーヘッドがゼロで、enumよりもツリーシェイキングに優れています