TypeScript as const assertion とは?型推論を固定する使い方と具体例
TypeScriptを使っていると、配列やオブジェクトの型推論に悩むことがあります。特に定数として定義した値なのに、型が予想外に広くなってしまう場面に遭遇するでしょう。そこで活躍するのが「as const assertion」です。
この記事では、as const assertionの基本から実践的な使い方、よくある間違いまで、初心者向けに丁寧に解説します。
原因の説明:なぜas constが必要なのか
通常の型推論の問題点
TypeScriptで値を定義するとき、明示的に型を指定しなければ、型推論によって自動的に型が決められます。しかし、この自動推論は時に思わぬ結果をもたらします。
例えば、以下のコードを見てください:
const colors = ['red', 'green', 'blue'];
このコードの場合、TypeScriptはcolorsの型をstring[]と推論します。つまり、「任意の文字列の配列」という意味です。しかし、実装者の意図は「赤、緑、青の3色だけに限定した配列」かもしれません。
同様に、オブジェクトでも問題が生じます:
const user = {
name: 'Taro',
age: 25
};
この場合、user.nameの型はstring、user.ageの型はnumberと推論されます。後からuser.name = 'Hanako'のように値を変更できる状態です。
as const assertionの役割
as constを使うと、TypeScriptに「この値を変更しない定数として扱い、最も具体的なリテラル型として推論してほしい」と伝えることができます。
リテラル型とは、特定の値だけを許可する型です。例えば、'red'という値だけを許可する型は'red'です。
const colors = ['red', 'green', 'blue'] as const;
// 型は: readonly ['red', 'green', 'blue']
このようにすることで、型チェッカーが「この配列は正確にこれら3つの値だけを持つ」と理解します。
解決手順:as constの使い方
ステップ1:基本的な使い方
as constを使うのは非常に簡単です。値の後ろにas constと書くだけです:
const value = '定数' as const;
ステップ2:配列に適用する
配列全体をリテラル型にしたい場合:
const statuses = ['active', 'inactive', 'pending'] as const;
// 型: readonly ['active', 'inactive', 'pending']
ステップ3:オブジェクトに適用する
オブジェクト全体をリテラル型にしたい場合:
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retryCount: 3
} as const;
// 各プロパティが具体的なリテラル型になる
ステップ4:ネストされた構造に適用する
深くネストされたオブジェクトにも有効です:
const appConfig = {
database: {
host: 'localhost',
port: 5432
},
cache: {
enabled: true,
ttl: 3600
}
} as const;
コード例:実践的な使用例
例1:ステータス管理
// as constを使わない場合(推奨されない)
const statuses1 = ['pending', 'approved', 'rejected'];
// 型: string[]
// as constを使う場合(推奨)
const statuses2 = ['pending', 'approved', 'rejected'] as const;
// 型: readonly ['pending', 'approved', 'rejected']
// ステータスの型定義
type Status = typeof statuses2[number];
// Type: 'pending' | 'approved' | 'rejected'
// 関数で使用
function handleStatus(status: Status) {
console.log(`Status: ${status}`);
}
handleStatus('pending'); // OK
handleStatus('approved'); // OK
handleStatus('unknown'); // エラー:型不一致
例2:ロールベースアクセス制御
const roles = ['admin', 'user', 'guest'] as const;
type Role = typeof roles[number];
// Type: 'admin' | 'user' | 'guest'
interface User {
id: number;
name: string;
role: Role;
}
const user: User = {
id: 1,
name: 'Taro',
role: 'admin'
};
user.role = 'superuser'; // エラー:許可された値のみ使用可能
例3:設定オブジェクト
const appSettings = {
features: {
darkMode: true,
notifications: false,
analytics: true
},
limits: {
maxUploadSize: 10485760, // 10MB
sessionTimeout: 1800000 // 30min
}
} as const;
// キーにアクセス
type FeatureKey = keyof typeof appSettings.features;
// Type: 'darkMode' | 'notifications' | 'analytics'
// 値にアクセス
type FeatureValue = typeof appSettings.features[FeatureKey];
// Type: boolean
function toggleFeature(feature: FeatureKey, enabled: boolean) {
console.log(`Feature ${feature}: ${enabled}`);
}
toggleFeature('darkMode', true); // OK
toggleFeature('invalidFeature', true); // エラー
例4:API レスポンスタイプの定義
const apiEndpoints = {
users: '/api/users',
posts: '/api/posts',
comments: '/api/comments'
} as const;
type Endpoint = typeof apiEndpoints[keyof typeof apiEndpoints];
// Type: '/api/users' | '/api/posts' | '/api/comments'
async function fetchData(endpoint: Endpoint) {
const response = await fetch(endpoint);
return response.json();
}
fetchData('/api/users'); // OK
fetchData('/api/invalid'); // エラー
例5:定数列挙体
const colors = {
RED: '#FF0000',
GREEN: '#00FF00',
BLUE: '#0000FF'
} as const;
type ColorCode = typeof colors[keyof typeof colors];
// Type: '#FF0000' | '#00FF00' | '#0000FF'
function applyColor(code: ColorCode) {
document.body.style.backgroundColor = code;
}
applyColor('#FF0000'); // OK
applyColor('#FFFFFF'); // エラー:定義された色のみ使用可能
よくある間違いと対策
間違い1:readonlyを誤解する
as constを使うと、配列やオブジェクトはreadonlyになります。これは「変更不可」という意味です。
const arr = [1, 2, 3] as const;
arr[0] = 10; // エラー:読み取り専用のため変更不可
arr.push(4); // エラー:配列の操作不可
対策: as constは「定数」を定義するときだけ使用してください。変更が必要な場合は、型注釈で対応します。
// 変更が必要な場合
const arr: (1 | 2 | 3)[] = [1, 2, 3];
arr[0] = 3; // OK
間違い2:配列の要素を誤認識
as constなしで配列を定義したとき、その要素型が予想と異なることがあります。
const tuple1 = [10, 'text', true];
// 型: (number | string | boolean)[]
const tuple2 = [10, 'text', true] as const;
// 型: readonly [10, 'text', true]
// tuple1では要素の型が曖昧
const first: string | number | boolean = tuple1[0]; // 型が広い
// tuple2では要素の型が正確
const first2: 10 = tuple2[0]; // 型が具体的
間違い3:型推論の活用忘れ
as constを使っても、推論結果を活用していない場合があります。
// 悪い例:as constを使っても型を手動で書く
const permissions = ['read', 'write', 'delete'] as const;
type Permission = 'read' | 'write' | 'delete'; // 手動で書くのは冗長
// 良い例:推論結果を活用
const permissions = ['read', 'write', 'delete'] as const;
type Permission = typeof permissions[number]; // 自動で推論
間違い4:オブジェクトのプロパティで部分的に使う
オブジェクト全体にas constを適用しないと、一部のプロパティが期待と異なる型になる可能性があります。
// 部分的な適用は不十分
const config = {
mode: 'production' as const,
debug: false
};
// config.debugは boolean のまま(false ではなく)
// 全体に適用
const config2 = {
mode: 'production',
debug: false
} as const;
// 両方とも具体的なリテラル型に
間白い5:ジェネリック型での混乱
as constの結果をジェネリック関数に渡すとき、型の変換に注意が必要です。
const items = ['apple', 'banana'] as const;
function process(arr: T): T {
return arr;
}
const result = process(items);
// resultの型: readonly ['apple', 'banana']
// 元の型情報が保持される
まとめ
as const assertionの要点:
- 目的: リテラル型を固定し、値の変更を防ぎながら型安全性を向上させる
- 使い方: 値の後ろに「as const」と記述するだけ
- 効果: 配列やオブジェクトの型推論が最も具体的になる
- メリット: 型を手動で定義する手間が減り、保守性が向上
- 注意点: readonlyになるため、値の変更が不可に
TypeScriptで型安全性を高めたいなら、as constは必須の機能です。特に、複数の箇所で使用される定数値やAPIエンドポイント、設定値などを定義するときに有効です。
初めは「何か難しそう」と感じるかもしれませんが、使っていくうちに「こんなに便利なら手放せない」と感じるようになるでしょう。ぜひプロジェクトで活用してみてください。

