TypeScript const アサーション完全解説 - より厳密な型推論を実現する
TypeScript 3.4 で導入された const アサーションは、型推論をより厳密にし、型安全性を向上させる強力な機能です。しかし、その真価を理解して使いこなしている開発者は多くありません。
この記事では、const アサーションの基本から実践的な活用方法まで詳しく解説します。
const アサーションとは
const アサーションは、as const という構文を使って、TypeScript に「この値をできるだけ厳密な型として扱ってほしい」と伝える機能です。
基本的な構文
// const アサーションを使わない場合
const colors = ['red', 'green', 'blue'];
// 型: string[]
// const アサーションを使う場合
const colors = ['red', 'green', 'blue'] as const;
// 型: readonly ['red', 'green', 'blue']
const アサーションの効果
const アサーションを使うと、以下の3つの効果があります。
1. リテラル型の保持
// ❌ 通常の推論
let status = 'success';
// 型: string
// ✅ const アサーション
let status = 'success' as const;
// 型: 'success'
// オブジェクトの場合
const config = {
timeout: 3000,
method: 'GET',
};
// 型: { timeout: number; method: string; }
const config = {
timeout: 3000,
method: 'GET',
} as const;
// 型: { readonly timeout: 3000; readonly method: 'GET'; }
2. プロパティの readonly 化
const point = { x: 10, y: 20 } as const;
// 型: { readonly x: 10; readonly y: 20; }
point.x = 30; // ❌ エラー: Cannot assign to 'x' because it is a read-only property
3. 配列のタプル化
// 通常の配列
const numbers = [1, 2, 3];
// 型: number[]
// const アサーション
const numbers = [1, 2, 3] as const;
// 型: readonly [1, 2, 3]
numbers.push(4); // ❌ エラー: Property 'push' does not exist on type 'readonly [1, 2, 3]'
実践的な活用例
1. 定数の定義
// ❌ 型安全性が低い
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE'];
type HttpMethod = typeof HTTP_METHODS[number]; // string
// ✅ リテラル型を保持
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE'] as const;
type HttpMethod = typeof HTTP_METHODS[number]; // 'GET' | 'POST' | 'PUT' | 'DELETE'
function request(method: HttpMethod) {
// method は厳密な型
}
request('GET'); // ✅
request('PATCH'); // ❌ エラー
2. ルーティング定義
const routes = {
home: '/',
about: '/about',
users: '/users',
userDetail: '/users/:id',
} as const;
type Route = typeof routes[keyof typeof routes];
// '/' | '/about' | '/users' | '/users/:id'
function navigate(route: Route) {
window.location.href = route;
}
navigate('/'); // ✅
navigate('/users'); // ✅
navigate('/invalid'); // ❌ エラー
3. API レスポンスの型定義
const API_STATUS = {
SUCCESS: 'success',
ERROR: 'error',
PENDING: 'pending',
} as const;
type ApiStatus = typeof API_STATUS[keyof typeof API_STATUS];
// 'success' | 'error' | 'pending'
interface ApiResponse<T> {
status: ApiStatus;
data?: T;
error?: string;
}
function handleResponse(response: ApiResponse<any>) {
if (response.status === 'success') {
// TypeScriptが正しく絞り込める
console.log(response.data);
}
}
4. 設定オブジェクト
const appConfig = {
api: {
baseUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
},
features: {
darkMode: true,
analytics: false,
},
} as const;
type AppConfig = typeof appConfig;
// 設定を型安全に使用
function getApiUrl(config: AppConfig): string {
return config.api.baseUrl; // 型: 'https://api.example.com'
}
// ディープな readonly
appConfig.api.timeout = 10000; // ❌ エラー
appConfig.features.darkMode = false; // ❌ エラー
5. Enum の代替
// ❌ 従来の Enum
enum Color {
Red = 'red',
Green = 'green',
Blue = 'blue',
}
// ✅ const アサーション(よりシンプル)
const Color = {
Red: 'red',
Green: 'green',
Blue: 'blue',
} as const;
type Color = typeof Color[keyof typeof Color];
// 'red' | 'green' | 'blue'
function setColor(color: Color) {
// ...
}
setColor(Color.Red); // ✅
setColor('red'); // ✅(値としても使える)
パターンとイディオム
1. ユニオン型の生成
const ROLES = ['admin', 'user', 'guest'] as const;
type Role = typeof ROLES[number];
// 'admin' | 'user' | 'guest'
function hasPermission(role: Role): boolean {
return role === 'admin';
}
2. キーのユニオン型
const translations = {
en: { hello: 'Hello', goodbye: 'Goodbye' },
ja: { hello: 'こんにちは', goodbye: 'さようなら' },
} as const;
type Language = keyof typeof translations;
// 'en' | 'ja'
type TranslationKey = keyof typeof translations.en;
// 'hello' | 'goodbye'
function translate(lang: Language, key: TranslationKey): string {
return translations[lang][key];
}
3. 関数のパラメータ制約
const SORT_OPTIONS = {
nameAsc: { field: 'name', order: 'asc' },
nameDesc: { field: 'name', order: 'desc' },
dateAsc: { field: 'date', order: 'asc' },
dateDesc: { field: 'date', order: 'desc' },
} as const;
type SortOption = keyof typeof SORT_OPTIONS;
function sortData(option: SortOption) {
const { field, order } = SORT_OPTIONS[option];
// field は 'name' | 'date'
// order は 'asc' | 'desc'
}
4. タプルのバリデーション
function parseCoordinates(input: string) {
const parts = input.split(',');
if (parts.length !== 2) {
throw new Error('Invalid coordinates');
}
return [parseFloat(parts[0]), parseFloat(parts[1])] as const;
// 戻り値の型: readonly [number, number]
}
const [x, y] = parseCoordinates('10,20');
// x: number, y: number
実践的なユースケース
React コンポーネントのProps
const BUTTON_VARIANTS = ['primary', 'secondary', 'danger'] as const;
const BUTTON_SIZES = ['sm', 'md', 'lg'] as const;
type ButtonVariant = typeof BUTTON_VARIANTS[number];
type ButtonSize = typeof BUTTON_SIZES[number];
interface ButtonProps {
variant?: ButtonVariant;
size?: ButtonSize;
children: React.ReactNode;
}
function Button({ variant = 'primary', size = 'md', children }: ButtonProps) {
return (
<button className={`btn-${variant} btn-${size}`}>
{children}
</button>
);
}
// 使用時に型チェック
<Button variant="primary" size="md">Click</Button> // ✅
<Button variant="invalid" size="xl">Click</Button> // ❌ エラー
Redux アクション
// アクションタイプの定義
const actionTypes = {
USER_LOGIN: 'USER_LOGIN',
USER_LOGOUT: 'USER_LOGOUT',
SET_THEME: 'SET_THEME',
} as const;
type ActionType = typeof actionTypes[keyof typeof actionTypes];
// アクション生成関数
function createAction<T extends ActionType, P>(type: T, payload: P) {
return { type, payload } as const;
}
// 使用例
const loginAction = createAction(actionTypes.USER_LOGIN, { id: 1, name: 'Alice' });
// 型: { readonly type: 'USER_LOGIN'; readonly payload: { id: number; name: string; } }
const logoutAction = createAction(actionTypes.USER_LOGOUT, undefined);
// 型: { readonly type: 'USER_LOGOUT'; readonly payload: undefined }
フォームのバリデーション
const VALIDATION_RULES = {
email: {
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: 'Invalid email address',
},
password: {
required: true,
minLength: 8,
message: 'Password must be at least 8 characters',
},
} as const;
type ValidationRules = typeof VALIDATION_RULES;
type FieldName = keyof ValidationRules;
function validate(field: FieldName, value: string): boolean {
const rule = VALIDATION_RULES[field];
if (rule.required && !value) {
console.error(rule.message);
return false;
}
if ('pattern' in rule && !rule.pattern.test(value)) {
console.error(rule.message);
return false;
}
if ('minLength' in rule && value.length < rule.minLength) {
console.error(rule.message);
return false;
}
return true;
}
API エンドポイントの定義
const API_ENDPOINTS = {
users: {
list: '/api/users',
detail: (id: number) => `/api/users/${id}`,
create: '/api/users',
update: (id: number) => `/api/users/${id}`,
delete: (id: number) => `/api/users/${id}`,
},
posts: {
list: '/api/posts',
detail: (id: number) => `/api/posts/${id}`,
},
} as const;
type ApiEndpoints = typeof API_ENDPOINTS;
async function fetchUser(id: number) {
const url = API_ENDPOINTS.users.detail(id);
const response = await fetch(url);
return response.json();
}
注意点とベストプラクティス
1. パフォーマンスへの影響
// ❌ 大きなオブジェクトに使うと型チェックが遅くなる可能性
const HUGE_CONFIG = {
// 数千行の設定...
} as const;
// ✅ 必要な部分だけに使う
const IMPORTANT_CONFIG = {
apiKey: 'xxx',
endpoints: ['/', '/api'],
} as const;
2. ジェネリクスとの組み合わせ
function createArray<T>(items: readonly T[]): T[] {
return [...items];
}
const numbers = createArray([1, 2, 3] as const);
// 型: number[](リテラル型は保持されない)
// より型安全な実装
function createTuple<T extends readonly unknown[]>(items: T): T {
return items;
}
const numbers2 = createTuple([1, 2, 3] as const);
// 型: readonly [1, 2, 3]
3. satisfies との併用(TypeScript 4.9+)
type Color = 'red' | 'green' | 'blue';
// ❌ 型エラーが起きない
const colors1 = ['red', 'green', 'yellow'] as const;
// ✅ satisfies で型チェック
const colors2 = ['red', 'green', 'blue'] as const satisfies readonly Color[];
const colors3 = ['red', 'green', 'yellow'] as const satisfies readonly Color[];
// ❌ エラー: 'yellow' は Color に含まれない
4. 型ヘルパーの作成
// readonly を深く適用
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
const config = {
api: {
url: 'https://example.com',
timeout: 5000,
},
} as const;
type Config = DeepReadonly<typeof config>;
まとめ
const アサーションは、TypeScript の型システムを最大限に活用するための強力なツールです。
主な利点:
- より厳密な型推論
- イミュータブルな値の定義
- リテラル型の保持
- Enum の代替として使える
使いどころ:
- 定数の定義
- 設定オブジェクト
- APIエンドポイント
- ルーティング定義
- バリデーションルール
適切に使用することで、より型安全で保守しやすいコードを書くことができます。