You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript递归提取嵌套路由路径类型的实现问题求助

如何在TypeScript中递归提取嵌套路由的所有路径类型

问题描述

我正尝试在TypeScript中构建一个递归类型,用于从给定的路由配置中解析所有可能的路由路径(包括嵌套路径),但遇到了类型约束与推断的问题。

已通过TypedRoute接口和createRoute函数定义了路由:

interface TypedRoute<
  Path extends string,
  QueryParams extends Record<string, string> = {}
> {
  path: Path;
  queryParams?: QueryParams;
  children?: TypedRoute<any, any>[];
}

function createRoute<
  Path extends string,
  QueryParams extends Record<string, string> = {}
>(config: TypedRoute<Path, QueryParams>) {
  return config;
}

const routes = [
  createRoute({
    path: 'results/:id/foo/:bar'
  }),
  createRoute({
    path: 'home',
    children: [
      createRoute({
        path: 'bar',
        children: [
          createRoute({ path: 'baz' })
        ]
      }),
    ],
  }),
]

尝试创建ExtractRoutePaths类型以递归提取所有可能路径:

type ExtractRoutePaths<T extends readonly TypedRoute<any, any>[]> = 
  T extends readonly []
    ? never
    : T extends readonly [infer First, ...infer Rest]
      ? First extends TypedRoute<infer P, any>
        ? First extends { children: infer C }
          ? C extends readonly TypedRoute<any, any>[]
            ? `{P}` | ExtractRoutePaths<C, `${P}/`> | ExtractRoutePaths<Rest extends readonly TypedRoute<any, any>[] ? Rest : never>
            : `${P}` | ExtractRoutePaths<Rest extends readonly TypedRoute<any, any>[] ? Rest : never>
          : `${P}` | ExtractRoutePaths<Rest extends readonly TypedRoute<any, any>[] ? Rest : never>
        : ExtractRoutePaths<Rest extends readonly TypedRoute<any, any>[] ? Rest : never>
      : never;

预期得到的路径类型为:results/:id/foo/:bar | home | home/bar | home/bar/baz

问题分析

你的代码存在以下几个关键问题:

  • 模板字符串语法错误:{P}应该是${P},这是TypeScript模板字符串类型的正确写法,原写法会被解析为普通字符串而非变量插值。
  • 泛型参数不匹配:你定义的ExtractRoutePaths只有一个泛型参数T,但调用时传入了两个参数(ExtractRoutePaths<C, ${P}/>),导致类型不兼容。
  • 缺少父路径拼接逻辑:没有传递父路径前缀来拼接子路径,无法生成home/bar这类嵌套路径。
  • 数组遍历逻辑不完善:原类型在处理数组剩余元素时的类型推断冗余,且没有正确处理子路由的递归拼接。

解决方案

我们需要调整泛型结构,增加一个可选的Prefix参数来传递父路径前缀,同时拆分逻辑为处理单个路由和处理路由数组两部分,让递归更清晰:

// 处理单个路由及其子路由的路径提取
type ExtractSingleRoutePaths<
  Route extends TypedRoute<any, any>,
  Prefix extends string = ''
> = 
  // 拼接当前路由的完整路径
  `${Prefix}${Route['path']}` | 
  // 如果有子路由,递归处理子路由,前缀为当前完整路径 + '/'
  (Route['children'] extends readonly TypedRoute<any, any>[] 
    ? ExtractRoutePaths<Route['children'], `${Prefix}${Route['path']}/`> 
    : never);

// 处理路由数组的路径提取
type ExtractRoutePaths<
  Routes extends readonly TypedRoute<any, any>[],
  Prefix extends string = ''
> = 
  Routes extends readonly [infer First, ...infer Rest]
    ? First extends TypedRoute<any, any>
      ? // 提取当前路由的所有路径 + 剩余路由的所有路径
        ExtractSingleRoutePaths<First, Prefix> | ExtractRoutePaths<Rest, Prefix>
      : ExtractRoutePaths<Rest, Prefix>
    : never;

测试该类型:

type AllRoutePaths = ExtractRoutePaths<typeof routes>;
// 类型结果:"results/:id/foo/:bar" | "home" | "home/bar" | "home/bar/baz"

完全符合预期的路径类型。

内容的提问来源于stack exchange,提问作者Js1234

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 04:42:02