TypeScriptテンプレートリテラル型で作る型安全なURLルーター

TypeScript Routing Frontend
結論

Template Literal Typesinfer キーワードを組み合わせ、再帰抽出型を作成します。

// TypeScript公式仕様:パラメータ抽出型の定義
type ExtractParams<Path extends string> =
  Path extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractParams<`/${Rest}`>
    : Path extends `${string}:${infer Param}`
    ? Param
    : never;

// 抽出したキーをオブジェクト型へ変換
type PathParams<T extends string> = {
  [K in ExtractParams<T>]: string;
};

TypeScript公式仕様:Template Literal Types の仕組み

TypeScript(4.1以上)で導入された Template Literal Types は、文字列型のリテラル("/users/:id")をパターンマッチングし、infer キーワードで任意のサブ文字列を取り出す機能です。

この機能を活用することで、react-routerNext.js のようなURLルーターに対して、型安全なパラメータ補完を提供できます。


実際に起こる事故:TS2589 コンパイルエラーの発生

複雑なネストや長すぎるURL文字列に対して型抽出を実行した際、TypeScriptコンパイラの再帰限界上限に達し、以下のエラーが発生するケースがあります。

TS2589: Type instantiation is excessively deep and possibly infinite.

発生原因と対策

TypeScriptは内部で型解析のループ数(Recursion Depth Limit)を制限しています。末尾の判定条件を甘く書くと無限ループとみなされるため、上記のように 「スラッシュありパターン」と「末尾単独パターン」を明確に分けて評価を終了させる記述(Tail Recursion 意識) が必須となります。


使用例と型補完の確認

function navigate<T extends string>(path: T, params: PathParams<T>) {
  // 実装ロジック
}

// ⭕ 型補完が効き、キー名(userId, postId)のタイポを静的に検出
navigate('/users/:userId/posts/:postId', {
  userId: '123',
  postId: '456',
});

// ❌ 存在しないキーを指定するとコンパイルエラーになる
navigate('/users/:userId', {
  invalidKey: '123', // Error: Object literal may only specify known properties
});