TypeScript:向子路由传递动态属性名并实现路径参数推断
解决TypeScript嵌套路由中路径参数的精确类型推断问题
问题分析
核心需求是在路由定义阶段就让处理函数精确推断出父级完整路径(dataPath)和当前路径(path),而非得到/${string}/${string}/${string}这类宽泛的字符串类型。当前实现的问题在于RawRouterDefn的索引签名使用了typeof K,TypeScript无法将其识别为具体的字符串字面量,只能推断为宽泛的string类型,导致路径参数无法精准捕获。
解决方案
调整类型定义,让每个路由层级的路径都以字符串字面量的形式被递归捕获,而非依赖宽泛的索引签名。关键是让RawRouterDefn的泛型参数明确传递当前层级的命名空间,同时用映射类型确保每个键都是具体的字面量:
// 处理函数类型:接收精确的dataPath和path字面量 type Handler<DataPath extends string, Path extends string> = ( args: { dataPath: DataPath; path: Path } ) => unknown; // 递归路由定义类型:每个层级都明确当前命名空间 type RawRouterDefn<Namespace extends string = ""> = { [K in string]: Handler<Namespace, K> | RawRouterDefn<`${Namespace}/${K}`>; }; // 最终路由类型:递归解析每个层级的路径 type Router<Routes extends RawRouterDefn, CurrentPath extends string = ""> = { [K in keyof Routes & string]: Routes[K] extends Handler<infer D, infer P> ? Handler<CurrentPath, K> : Router<Extract<Routes[K], RawRouterDefn>, `${CurrentPath}/${K}`>; }; // 创建路由的工厂函数,保持泛型约束 function createRouter<T extends RawRouterDefn>(rawDefn: T): Router<T, ""> { return {} as unknown as Router<T, "">; } // 测试:定义阶段即可精确推断参数类型 const router = createRouter({ some: { deeply: { nested: { // dataPath被推断为"some/deeply/nested",path为"route" route: ({ dataPath, path }) => { type CheckDataPath = typeof dataPath; // "some/deeply/nested" type CheckPath = typeof path; // "route" }, }, }, }, }); // 验证最终路由的参数类型 const route = router.some.deeply.nested.route; type Params = Parameters<typeof route>[0]; // Params类型为 { dataPath: "some/deeply/nested"; path: "route" }
关键改动说明
- 调整
RawRouterDefn的索引签名:将typeof K替换为直接使用K,让TypeScript能够捕获每个键的具体字符串字面量,而非宽泛的string。 - 优化
Router类型的条件判断:通过Extract确保子路由的类型被正确递归解析,同时明确传递当前层级的路径。 - 处理函数定义阶段,TypeScript就能根据嵌套层级精确推断
dataPath和path的字面量类型,无需等到路由创建完成后再提取参数类型。
内容的提问来源于stack exchange,提问作者Inkblot
相关产品推荐
相关产品推荐

