TypeScript as const とは?型安全性を高める使い方を徹底解説
TypeScript を使っていると「as const」という記法を目にすることがあります。この機能は初心者には理解しにくいかもしれませんが、型安全性を大幅に向上させる強力なツールです。本記事では、as const の基本から実践的な使い方まで、わかりやすく解説します。
as const とは何か:原因の説明
TypeScript の型推論の基本
TypeScript を書くとき、変数に値を代入するとコンパイラが自動的に型を推論します。例えば、以下のコードを見てください。
const message = "Hello";
// message の型は string と推論される
ここで重要なのは、message は「Hello」という具体的な値を持つ変数ですが、TypeScript は型を「string」という広い範囲で推論してしまうということです。これが問題の源です。
as const が解決する問題
実務では、特定の値だけを許可したい場合があります。例えば、ステータスが「success」「error」「loading」の3つだけに限定したいとします。
const status = "success";
// 問題:status は string 型として推論される
// これにより、誤った値を代入してもエラーにならない
status = "invalid"; // 型チェックではエラーにならない
このような場合に「as const」を使うことで、値を「リテラル型」として固定化できます。
const status = "success" as const;
// status の型は "success" に固定される
// より狭く、より具体的な型になる
as const は、TypeScript に「この値は変更しないので、最も狭い型として推論してほしい」と指示する機能なのです。
as const の使い方:解決手順
ステップ1:基本的な使い方を理解する
as const を使う最もシンプルな例から始めましょう。
// as const を使わない場合
const color1 = "red";
// color1 の型:string
// as const を使う場合
const color2 = "red" as const;
// color2 の型:"red" (リテラル型)
このように、as const を付けることで、型が「string」という広い型から「”red”」という狭い型に変わります。
ステップ2:オブジェクトに as const を適用する
単純な値だけでなく、オブジェクトにも as const を使えます。これが非常に便利です。
// as const を使わない場合
const user = {
name: "Taro",
age: 25,
role: "admin"
};
// user の型:{ name: string; age: number; role: string }
// as const を使う場合
const user = {
name: "Taro",
age: 25,
role: "admin"
} as const;
// user の型:{ readonly name: "Taro"; readonly age: 25; readonly role: "admin" }
as const を使うことで、すべてのプロパティが readonly となり、値も具体的なリテラル型で固定されます。
ステップ3:配列に as const を適用する
配列に対しても as const は有効です。
// as const を使わない場合
const colors = ["red", "green", "blue"];
// colors の型:(string)[]
// as const を使う場合
const colors = ["red", "green", "blue"] as const;
// colors の型:readonly ["red", "green", "blue"]
配列の要素が具体的な値として型に反映されるので、要素の追加や変更を防げます。
コード例:実践的な使用方法
例1:API レスポンスのステータス管理
API の成功・失敗を管理するコードで、as const の威力が発揮されます。
// ステータスの定義
const API_STATUS = {
SUCCESS: "success",
ERROR: "error",
LOADING: "loading"
} as const;
// ステータス型の抽出
type ApiStatus = typeof API_STATUS[keyof typeof API_STATUS];
// ApiStatus = "success" | "error" | "loading"
// 関数での使用
function handleResponse(status: ApiStatus) {
if (status === "success") {
console.log("処理成功");
} else if (status === "error") {
console.log("エラー発生");
} else if (status === "loading") {
console.log("読み込み中");
}
}
// 正しい使用
handleResponse(API_STATUS.SUCCESS); // OK
// 間違った使用はエラーになる
handleResponse("invalid"); // エラー!
例2:ユーザーロールの制限
// ロール定義
const USER_ROLES = ["admin", "user", "guest"] as const;
// ロール型の抽出
type UserRole = typeof USER_ROLES[number];
// UserRole = "admin" | "user" | "guest"
interface User {
id: number;
name: string;
role: UserRole;
}
const users: User[] = [
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "user" },
{ id: 3, name: "Charlie", role: "guest" }
];
// 権限チェック関数
function hasPermission(role: UserRole, action: string): boolean {
const permissions: Record = {
admin: ["read", "write", "delete"],
user: ["read", "write"],
guest: ["read"]
};
return permissions[role].includes(action);
}
// 使用例
console.log(hasPermission("admin", "delete")); // true
console.log(hasPermission("guest", "write")); // false
例3:設定オブジェクトの型安全性
// アプリケーション設定
const APP_CONFIG = {
env: "production" as const,
port: 3000 as const,
debug: false as const,
features: {
darkMode: true,
notifications: true
} as const
} as const;
// 型の抽出
type AppConfig = typeof APP_CONFIG;
type Environment = typeof APP_CONFIG.env;
// Environment = "production"
// 設定値へのアクセス
const env: Environment = APP_CONFIG.env;
// 誤った変更を防ぐ
// APP_CONFIG.port = 8000; // エラー!readonly
function getConfig(): AppConfig {
return APP_CONFIG;
}
const config = getConfig();
console.log(config.port); // 3000
よくある間違い
間違い1:as const を忘れて型推論に頼る
// ❌ 間違い
const statusOptions = ["pending", "approved", "rejected"];
// statusOptions の型:string[]
// 型チェックが弱くなり、タイプミスに気づきません
function updateStatus(status: string) {
// status に "invalid" が渡される可能性がある
}
updateStatus("invalid"); // エラーにならない
// ✅ 正しい
const statusOptions = ["pending", "approved", "rejected"] as const;
// statusOptions の型:readonly ["pending", "approved", "rejected"]
type Status = typeof statusOptions[number];
function updateStatus(status: Status) {
// Status = "pending" | "approved" | "rejected"
// のみが許可される
}
updateStatus("invalid"); // エラー!
間違い2:オブジェクトの一部だけに as const を使う
// ❌ 間違い
const config = {
name: "MyApp" as const,
version: 1.0, // as const がない
features: ["auth", "api"] // as const がない
};
// name は "MyApp" 型ですが、version と features は広い型のまま
// ✅ 正しい
const config = {
name: "MyApp",
version: 1.0,
features: ["auth", "api"]
} as const;
// 全てのプロパティが具体的なリテラル型になる
間違い3:let で宣言した変数に as const を使う
// ❌ 間違い(意味がない)
let status = "pending" as const;
status = "approved"; // 値を変更できてしまう
// as const は readonly を保証するが、let では変更可能
// ✅ 正しい
const status = "pending" as const;
// status = "approved"; // エラー!
// const と as const で二重の保護
間違い4:関数の戻り値の型を正しく指定しない
// ❌ 間違い
function getTheme() {
const theme = { mode: "dark", colors: ["#000", "#fff"] } as const;
return theme;
}
// 戻り値の型が自動推論されるが、明示的でない
const myTheme = getTheme();
// myTheme の型が不確かになる可能性がある
// ✅ 正しい
const THEME = { mode: "dark", colors: ["#000", "#fff"] } as const;
type Theme = typeof THEME;
function getTheme(): Theme {
return THEME;
}
const myTheme = getTheme();
// myTheme の型が明確:{ readonly mode: "dark"; readonly colors: readonly ["#000", "#fff"]; }
as const の応用的な活用
Union 型の作成
// ボタンのバリエーションを定義
const BUTTON_VARIANTS = ["primary", "secondary", "danger"] as const;
// Union 型を自動生成
type ButtonVariant = typeof BUTTON_VARIANTS[number];
// ButtonVariant = "primary" | "secondary" | "danger"
interface ButtonProps {
variant: ButtonVariant;
label: string;
}
const button: ButtonProps = {
variant: "primary", // OK
label: "Click me"
};
// const button2: ButtonProps = {
// variant: "invalid", // エラー!
// label: "Click me"
// };
列挙型の代わりとして
// as const で enum の代わりになる
const HTTP_STATUS = {
OK: 200,
CREATED: 201,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
NOT_FOUND: 404,
SERVER_ERROR: 500
} as const;
type HttpStatus = typeof HTTP_STATUS[keyof typeof HTTP_STATUS];
// HttpStatus = 200 | 201 | 400 | 401 | 404 | 500
function handleStatus(code: HttpStatus) {
switch(code) {
case HTTP_STATUS.OK:
console.log("成功");
break;
case HTTP_STATUS.NOT_FOUND:
console.log("見つかりません");
break;
default:
console.log("その他");
}
}
handleStatus(HTTP_STATUS.OK); // OK
// handleStatus(999); // エラー!
as const 使用時の注意点
パフォーマンスへの影響
as const 自体はコンパイル時に処理されるため、実行時のパフォーマンスに影響はありません。型チェックの厳密さが増すだけです。
互換性の問題
as const は TypeScript 3.4 以降で利用可能です。古いバージョンを使っている場合はアップグレードが必要です。
readonly の理解
as const を使うと、オブジェクトや配列は readonly になります。これは値の変更を防ぐという利点がありますが、既存コードとの互換性を考慮する必要があります。
const config = { name: "App", port: 3000 } as const;
// config.name = "NewApp"; // エラー!readonly
// config.port = 8000; // エラー!readonly
// 値を変更したい場合は新しいオブジェクトを作成
const newConfig = { ...config, port: 8000 };
// newConfig の型は { readonly name: "App"; readonly port: 8000; }
まとめ
TypeScript の as const は、型安全性を大幅に向上させる強力な機能です。
主なポイント:
- as const は値をリテラル型として固定化する
- 単純な値、オブジェクト、配列に使用できる
- 型推論をより厳密にして、タイプミスを防ぐ
- const と組み合わせることで、値の変更を完全に防ぐ
- Union 型や enum の代替として活用できる
- let ではなく const で宣言した時に意味を持つ
- オブジェクト全体に as const を使うのが推奨される
最初は理解しにくいかもしれませんが、as const を積極的に使うことで、バグの少ないより堅牢なTypeScript コードが書けるようになります。特に、ステータス定義やロール管理、API のレスポンス型定義など、値が固定されるべき場面では as const の使用を心がけましょう。
実際のプロジェクトで使ってみることで、その効果を実感できるはずです。ぜひ今日から as const を活用してください!

