赤い波線のその先へ本番リリースに向けて権限モジュールのリファクタリングをしていたところ、TypeScriptに足止めされました。設定オブジェクトを完全に不変(immutable)なリテラル型として扱いたいと考えたのですが、期待していたクリーンな読み取り専用型になる代わりに、コンパイラはよくあるエラーを返しました:A 'const' assertion can only be applied to a string, number, boolean, array, or object literal.
これは、TypeScriptがリテラル式として認識しなくなった値に対して as const を適用しようとしたときに発生します。大規模プロジェクトで型安全性を強化する際によく遭遇する壁です。
エラーメッセージ```
// 通常、このコードがエラーを発生させます: const userRoles = ['admin', 'editor', 'viewer']; const ROLES = userRoles as const; // ❌ ここでエラー!
コンパイラの出力は直接的ですが、型の widening(型拡張)について知らないと混乱するかもしれません:
error TS1355: A 'const' assertion can only be applied to a string, number, boolean, array, or object literal.
## 根本的な原因:型の WideningTypeScriptはバージョン3.4で `as const` アサーションを導入しました。その主な役割は、式をリテラル型(例えば、汎用的な `string` ではなく特定の文字列 "admin" など)として扱い、すべてのメンバーを `readonly` にするようコンパイラに伝えることです。
しかし、**`as const` は作成時に適用する必要があります。**
上記の例では、`userRoles` はすでに変数になっています。2行目に到達するまでに、TypeScriptはすでに `userRoles` を一般的な型 `string[]` に「拡張(widen)」しています。リテラルとしての具体性をすでに失った参照に対して、`const` アサーションを適用することはできません。コンパイラは、データ構造そのものに直接アサーションを記述することを要求します。
## 修正方法### アプローチ 1:ソース(定義時)でアサーションする最も効果的な修正方法は、`as const` アサーションを変数に代入する前のリテラル値に移動することです。これにより、コンパイラが型を拡張するのを防ぐことができます。
// ✅ 代わりにこのようにします const ROLES = ['admin', 'editor', 'viewer'] as const;
// ROLES の型は: readonly ["admin", "editor", "viewer"] になります
配列の括弧の直後にアサーションを置くことで、これら3つの特定の文字列をそのまま保持するようにTypeScriptに伝えます。これにより、型が無限の文字列の集合から、固定された3つのタプルへと絞り込まれます。
### アプローチ 2:三項演算子とロジックの処理三項演算子やテンプレートリテラルを使用しているときに、このエラーに遭遇することがあります。式が複雑な場合、コンパイラがリテラル値を追跡できなくなることがあります。
// ❌ 複雑なシナリオでは失敗することが多いです const status = (isActive ? 'active' : 'inactive') as const;
// ✅ 修正:個別のリテラルに対してアサーションを行う const status = isActive ? ('active' as const) : ('inactive' as const);
最近のTypeScriptバージョンでは単純な三項演算子をより適切に処理できるようになっていますが、個々の結果を括弧で囲むのがリテラル型を保証する最も安全な方法です。
### アプローチ 3:'satisfies' 演算子 (TS 4.9+)TypeScript 4.9以降を使用している場合、`satisfies` 演算子は多くの場合 `as const` よりも優れています。これにより、リテラル型を保持したままデータ構造を検証できます。
type Config = Record<string, string[]>;
// ✅ 両方の良いとこ取り const menu = { home: ['/'], auth: ['/login', '/signup'] } as const satisfies Config;
このアプローチにより、オブジェクトが `readonly` になり、特定の文字列値が保持され、さらにオブジェクトが `Config` インターフェースに適合していることが確認されます。
### アプローチ 4:テンプレートリテラルの詳細動的な文字列を作成する場合、基本となるコンポーネントがリテラルであることを確認してください。プレフィックスがすでに一般的な `string` に拡張されている場合、最終的なテンプレートリテラルを定数(constant)にアサーションし直すことはできません。
const prefix = 'API'; // デフォルトでリテラル "API" になります
const endpoint = ${prefix}_URL as const; // これは機能します!
## 検証手順修正を確認するには、VS Codeで変数にマウスホバーしてください。型定義に `string` や `string[]` だけでなく、具体的な値が表示されるはずです。
- **成功:** `readonly ["value1", "value2"]` と表示される。- **失敗:** 依然として `string[]` のような汎用的な型が表示される。確実に確認するために、クイック型チェックを実行してください:
npx tsc --noEmit
## 予防策- **早めに宣言する:** 設定オブジェクトを定義した瞬間に `as const` を適用します。- **Readonlyを優先する:** 関数のシグネチャで `ReadonlyArray<T>` を使用し、不変性の要件を明示します。- **'let' を避ける:** 後で値が変わるとコンパイラに判断されないよう、すべての変数宣言に `const` を使用します。

