TypeScriptの新しい const 型パラメータ 構文を使用すると、呼び出し側に as const を至る所に散りばめさせることなく、関数内でリテラル型をそのまま保持できるため、型拡充(widening)による最も一般的なバグの原因を排除できます。
ジェネリックなコードを悩ませる「型の拡充」問題
ジェネリックな関数がオブジェクトリテラルを受け取ると、コンパイラはリテラルプロパティをより広いプリミティブ型へと拡充(widen)してしまいます。
function call<T>(arg: T) {}
call({ method: "GET" }) // T is inferred as { method: string }
リテラル "GET" は string に縮退します。判別共用体(discriminated unions)やテンプレートリテラルの抽出など、正確な値に依存する後続のコードは、型が正確なリテラルを保持しなくなるため、動作しなくなります。開発者はこれまで、呼び出し側で { method: "GET" } as const と記述することでこの問題を回避してきました。これはコンパイラにリテラルを保持するよう指示するものですが、その修正は関数の定義ではなく、呼び出し側の手に委ねられています。
const 型パラメータ:シグネチャレベルでの修正
型パラメータに付与される新しい const 修飾子は、そのジェネリック引数に対して*可能な限り狭い(narrowest)*型を推論するようにコンパイラに指示します。関数を function foo<const T>(arg: T) と宣言すると、T は呼び出し側が as const を記述したかのように自動的に振る舞います。
- 文字列、数値、booleanのリテラルは、正確な値のまま保持されます(
stringではなく"GET")。 - 配列は、各要素が正確に型付けされた readonly タプルになります。
- オブジェクトは、すべてのネストレベルでリテラル型を保持する、深い階層まで readonly な構造に変わります。
制約が関数のシグネチャに含まれているため、すべての呼び出し側が自動的にその恩恵を受けられます。キャストを忘れたとしても、型システムの不健全性(unsoundness)を招くことはありません。
なぜ従来の as const ハックよりも優れているのか
as const は呼び出し側の解決策です。ジェネリック関数を利用するすべてのユーザーが、アサーションを追加することを覚えておく必要があります。一度でも呼び出しを忘れると、型安全性は失われてしまいます。const 型パラメータは、その責任を API 設計そのものへと移します。関数が「渡されるものに対して、可能な限り狭い形状が必要である」と宣言し、コンパイラがそれを強制するのです。
この変化は、ジェネリックなビルダー、設定ファクトリ、あるいはフィールドのリテラル値が型ロジックを駆動するような API を提供するライブラリやユーティリティにとって、非常に重要です。ライブラリの作者は、後続のコードを監視することなく、正しい推論を保証できるようになります。
恩恵を受ける実用的なシナリオ
- 設定ビルダー – 環境名 (
"dev" | "prod") がリテラルのまま保持されるため、追加のキャストなしで判別共用体によるチェックが可能になります。 - API ルート定義 – パス文字列が正確に保持されるため、テンプレートリテラル型を使用してパラメータを抽出できます (
"/users/:id"→\/users/${string}``)。 - ステートマシンヘルパー – メソッドチェーンを通じて状態識別子が固定リテラのまま維持され、意図しない状態の不一致を防ぎます。
いずれの場合も、const パラメータは繰り返される as const のボイラープレートを排除し、微妙なバグが紛れ込む可能性を低減します。
satisfies 演算子との組み合わせ
satisfies 演算子は、元のリテラル情報を保持したまま、値が構造的部分型(structural type)に適合しているかを検証します。これらを組み合わせて使用することで、両方の利点を得ることができます。const パラメータは狭い推論を提供し、satisfies は値が必要な形状を満たしていることを保証します。
function makeConfig<const C>(cfg: C) {
// cfg is inferred with exact literals
}
const cfg = {
env: "staging",
ports: [8080, 8443],
} satisfies { env: string; ports: number[] };
makeConfig(cfg); // works, literals stay intact
as const を使い続けるべき場面
const パラメータが真価を発揮するのは、関数のシグネチャを自分で制御できる場合です。修飾子がついていないサードパーティの関数を扱う場合や、ローカル変数に対して一時的にリテラルを保持したい場合は、引き続き as const が適切なツールとなります。呼び出し先の API を変更することなく値を固定するための、標準的な方法として機能し続けます。
今後の注目点
この機能はまだ新しいため、ツールやコミュニティのパターンは進化の過程にあります。オートコンプリートやクイックフィックスの提案に新しい構文が表示されるよう、IDE サポートのアップデートが期待されます。また、ライブラリのメンテナにも注目してください。多くのライブラリが公開されているジェネリックを const パラメータへと移行し始めるでしょう。これにより、以前は明示的な as const キャストに依存していたコードにおいて、破壊的変更が発生する可能性があります。
まとめ: リテラルの保持を関数の型パラメータに直接組み込むことで、TypeScript の const 型パラメータは、型拡充エラーの一般的な原因を取り除き、安全性の責任を呼び出し側から API 設計者へと戻します。自身で管理するジェネリックなエントリポイントにはこれを使用し、as const はローカルな値や外部 API 用に取っておきましょう。
