こんにちは!フロントエンドからバックエンドまで、TypeScriptの荒波を日々乗りこなしているシニアアーキテクトの私です。
今回は、TypeScript 5.0で導入された「const型パラメータ(Const Type Parameters)」という、知っているとコードの質が一段も二段も跳ね上がる強力なテクニックについてお話ししますね。
「関数に渡したリテラル値が、なぜか勝手に型を広げられて困った……」
そんな経験はありませんか?ここをクリアすれば、TypeScriptの型推論の仕組みがグッと深く理解できるようになりますよ。それでは、一緒に見ていきましょう!
—
1. なぜ「型が勝手に広がる(Widening)」と困るのか?
まずは、TypeScriptの基本的な型推論のおさらいから始めましょう。普段、私たちは次のような関数を書くと思います。
function printConfig(config: { endpoint: string; method: ‘GET’ | ‘POST’ }) {
console.log(`Sending ${config.method} to ${config.endpoint}`);
}
// これは普通に動きます
printConfig({ endpoint: ‘/api/users’, method: ‘GET’ });
ここで、次のようなコードを書いたとします。
const userSetting = {
endpoint: ‘/api/v1/data’,
method: ‘POST’ as const, // 無理やりconstアサーションを書いている
};
printConfig(userSetting);
「あれ、`method`に `as const` を書かないとエラーになるな……?」と思ったことはありませんか?
実は、TypeScriptはオブジェクトや変数の初期化の際、後から値を書き換えられるように、型を一般的なものに自動で拡大(Widening:拡大変換)する性質を持っています。
具体的には、こうなります。
- 書いた値: `’POST’` (特定のリテラル型)
- 推論される型: `string` (一般的な型)
そのため、関数側が `’GET’ | ‘POST’` のような厳密なリテラル型を求めている場合、引数に渡した瞬間に「`string`型は `POST` 型には代入できません」と怒られてしまうのです。
—
2. 救世主「const型パラメータ」の登場(TypeScript 5.0〜)
「関数に渡すオブジェクトや配列の構造を保ったまま、リテラル型をそのまま維持したい!」
その願いをスマートに叶えてくれるのが、TypeScript 5.0で追加された const型パラメータ です。
書き方はとてもシンプル。ジェネリクス(型引数)の先頭に `const` をつけるだけです。
// T の前に `const` をつける!
function createConfig
return config;
}
const myConfig = createConfig({
endpoint: ‘/api/v2/items’,
method: ‘GET’,
});
// マウスオーバーして型を確認してみてください:
// T は { readonly endpoint: “/api/v2/items”; readonly method: “GET”; } になっています!
なんと、`as const` を外側からわざわざ付けなくても、関数に渡しただけで中のプリミティブ値が自動的にイミュータブル(読み取り専用)なリテラル型として推論されました。これが const型パラメータの魔力です。
内部で何が起きているのか?(コンパイラの視点)
通常のジェネリクスでは、TypeScriptは渡された値から「一般的な型(`string`や`number`など)」を抽出しようとします。しかし、`
> 「この型パラメータには、変数宣言で `as const` を使った時と同じルールを適用し、可能な限り細かなリテラル型としてキャプチャしなさい」
これにより、実行時の値を完璧に反映した型安全な関数を作ることができるんです。
—
3. 実用例:型安全なルーティング設定ヘルパーを作ってみよう
もう少し実務に近い例を見てみましょう。
あらかじめ定義された許可されたルート(Path)のリストを受け取り、それをそのまま型として保持するヘルパー関数を作ってみます。
// 許可されたHTTPメソッドの定義
type HttpMethod = ‘GET’ | ‘POST’ | ‘PUT’ | ‘DELETE’;
// const型パラメータを使ったルート定義関数
function defineRoutes
return {
routes,
isSupported(path: string): boolean {
return routes.includes(path);
},
};
}
// 使用例
const appRoutes = defineRoutes([‘/home’, ‘/about’, ‘/contact’] as const);
// appRoutes.routes は readonly [“/home”, “/about”, “/contact”] 型になります。
// 単なる string[] ではなく、要素ごとのリテラル型が完璧に保持されています!
もしここで、`as const` を書き忘れたとしても、関数側に `
—
4. 陥りやすい文法エラーと注意点
非常に便利なconst型パラメータですが、初心者のうちは少しだけつまずきやすいポイントがあります。
エラーパターン1: `const` の位置を間違える
ジェネリクスを書くとき、`const` をどこに置くべきか迷うことがあります。
// ❌ 誤った書き方(コンパイルエラーになります)
function badFunc
// ⭕️ 正しい書き方(型変数の名前の「前」に置く)
function goodFunc
`const T` はひと固まりの構文だと覚えておいてくださいね。
エラーパターン2: 可変(Mutable)な再代入をしようとする
const型パラメータによって推論された型は、基本的にすべて `readonly`(読み取り専用)になります。そのため、関数内で引数のプロパティを書き換えようとすると、TypeScriptがすかさずブロックします。
function processOptions
// ❌ エラー: readonly のためプロパティを書き換えられません
// options.debug = true;
}
これは「データの不変性(Immutability)」を保つ上でも非常に良い挙動です。副作用のない、予測しやすいコードを書くための強力なバックアップになります。
—
まとめ
いかがでしたでしょうか? 今回のポイントをギュッと凝縮して振り返ってみましょう。
1. Widening(型の拡大変換)の悩み: 通常、オブジェクトや変数はリテラル型から一般的な型(`string`など)に広げられてしまう。
2. const型パラメータ(`
3. メリット: 余計な `as const` アサーションを書く必要が減り、より直感的かつ型安全なAPI設計が可能になる。
ここをクリアできれば、TypeScriptの型推論の仕組みをかなり使いこなせている証拠です。自信を持って、日々の開発に取り入れてみてくださいね。
あなたのTypeScriptライフが、より快適でバグのないものになりますように。それではまた!