こんにちは!フロントエンドからバックエンドまで、TypeScriptの荒波を日々乗りこなしているシニアアーキテクトの先輩です。
今回は、TypeScript 5.0で導入されて以来、API設計の常識を根底から変えた「const型パラメータ(const type parameters)」という強力な武器について解説します。
「関数の引数に渡したリテラル値が、勝手に型を広げられて困った……」
そんな悔しい思いをしたことはありませんか? ここをクリアすれば、あなたのTypeScriptスキルは一段上のステージに到達します。さっそく、その本質を一緒に紐解いていきましょう!
—
1. なぜ「型の広がり(Widening)」は起きるのか?
まずは、TypeScriptが普段裏側で何をやっているのか、その「思考の癖」を知ることから始めましょう。
通常、私たちが関数に文字列や数値、オブジェクトなどのリテラル値を渡すと、TypeScriptは「後から別の値が代入されるかもしれない」という親切心(あるいは余計なお世話)から、その型を汎用的な型(`string`や`number`、`object`など)に自動的に広げます。これを型Widening(ワイドニング)と呼びます。
従来の挙動を見てみよう
// 普通の関数
function handleConfig(config: { endpoint: string; timeout: number }) {
return config;
}
// 呼び出し
const myConfig = handleConfig({
endpoint: “/api/v1/users”,
timeout: 5000,
});
// 【ここで問題発生!】
// myConfig.endpoint の型は、”/api/v1/users” という厳密なリテラル型ではなく、
// 単なる `string` に広げられてしまっています。
「いやいや、私は `/api/v1/users` という特定の文字列型として扱いたいんだよ!」という場面、実務では本当によくありますよね。従来のTypeScriptでは、これを防ぐために `as const` を呼び出し側で強要したり、面倒なジェネリクスの制約を書く必要がありました。
—
2. 救世主登場:TypeScript 5.0の「const型パラメータ」
この問題を美しく、そして根本から解決してくれるのが `const` 修飾子付きの型パラメータ(`const T`) です。
型パラメータの前に `const` を置くことで、TypeScriptのコンパイラに対して「呼び出し時に渡された実引数のリテラル構造を、一切広げずにそのまま型としてキャプチャしなさい」と命じることができます。
基本的な書き方
// 型パラメータ T の前に `const` をつける!
function createRoute
return route;
}
const appRoute = createRoute({
path: “/dashboard”,
methods: [“GET”, “POST”],
} as const); // ※TS 5.0以降なら、ここすらの `as const` すら不要になることが多いです!
実際にTypeScript 5.0以降の環境で `const T` を使うと、呼び出し側の `as const` すら省略して、次のような厳密な型推論を得ることができます。
function lockValue
return val;
}
const result = lockValue({
mode: “strict”,
retries: 3,
});
// 🧠 実行時の型はどう評価されているか?
// result の型は以下のようになります:
// {
// readonly mode: “strict”;
// readonly retries: 3;
// }
お気づきでしょうか? 値が自動的に `readonly`(読み取り専用)になり、リテラル型が完璧に保たれていますよね。これが `const` 型パラメータの真骨頂です。
—
3. 実践!型安全な設定ビルダーを作ってみる
では、この機能を現場でどう活かすのか。実用的な例として、「型安全なアクション定義ヘルパー」を作ってみましょう。
// アクションの定義を受け取り、そのまま型を固定して返す関数
p
function defineActions
return actions;
}
// 使用例
const userActions = defineActions({
LOGIN: “AUTH_LOGIN”,
LOGOUT: “AUTH_LOGOUT”,
UPDATE_PROFILE: “USER_UPDATE”,
});
// 🎯 ここがすごい!
// userActions.LOGIN の型は単なる `string` ではなく、”AUTH_LOGIN” というリテラル型です。
type LoginAction = typeof userActions.LOGIN; // “AUTH_LOGIN” 型になる!
もしここで `const T` を付け忘れていると、`userActions.LOGIN` の型は単なる `string` になってしまい、後から型を利用して他の処理を厳密に縛り付けることができなくなってしまいます。
—
4. 陥りやすい罠と注意点
非常に便利な `const` 型パラメータですが、使い所を間違えるとコンパイルエラーや予期せぬ型崩れを起こします。よくあるつまずきポイントを確認しておきましょう。
罠1: 変数経由で渡すとWideningされることがある
関数に直接オブジェクトリテラルを渡さず、一度別の変数に代入してから渡す場合、その変数の型付けによってはリテラルが失われます。
const rawConfig = { env: “production” }; // 型は { env: string } になる
const res = lockValue(rawConfig);
// ❌ res.env は “production” ではなく string になってしまう!
対策: 変数側にも `as const` をつけるか、関数に直接リテラルを渡すようにしましょう。
罠2: 通常のジェネリクスとの混同
`const T` は、あくまで「実引数のリテラルをそのままキャプチャしたい」ためのものです。関数の戻り値を柔軟に変えたい場合や、複数の引数の型を関連付けたい複雑な型推論においては、通常のジェネリクスの方が適している場合もあります。適材適所で使い分けましょう。
—
まとめ:ここをクリアすればTypeScriptの基本はバッチリ!
今回は、TypeScript 5.0の隠れた名機能「const型パラメータ」について解説しました。
- 型の広がり(Widening):TypeScriptの親切心だが、リテラルを保持したい時には邪魔になる。
- `const T` の導入:型パラメータに `const` をつけることで、リテラル値や `readonly` 構造をそのまま型として固定できる。
- API設計への応用:設定値やルーティング、アクション定義など、「絶対に値を変えたくない・正確な型として利用したい」場面で絶大な効果を発揮する。
この概念をマスターすると、単に「エラーを防ぐためのTypeScript」から、「型によってコードの安全性と表現力を極限まで高めるTypeScript」へと、視界が一気に開けます。
日々のコーディングでぜひ取り入れてみてくださいね。それでは、次のステップへ進みましょう!