如何为带可选子属性的路由配置实现类型安全映射
TypeScript路由配置对象转换的类型安全实现
问题说明
当前实现存在两处TypeScript类型错误:
- 递归类型
Routes<T[Property]['children']>未处理children为undefined的情况,导致递归无法终止 - 无法直接将
{}赋值给Routes<T>类型的obj,且不想使用@ts-ignore或any规避类型检查
解决方案
1. 修复递归终止条件
通过条件类型判断children是否为有效对象,仅当它是可遍历的路由配置时才继续递归,否则用never终止递归:
type Routes<T extends Record<string, any>> = { [K in keyof T]: { path: string; children: T[K]['children'] extends Record<string, any> ? Routes<T[K]['children']> : never; }; };
2. 正确初始化目标对象
使用类型断言结合动态遍历构建对象,避免直接赋值空对象导致的类型不兼容:
function convertRoutes<T extends Record<string, any>>(routes: T): Routes<T> { const obj = {} as Routes<T>; for (const key in routes) { if (Object.prototype.hasOwnProperty.call(routes, key)) { const route = routes[key]; obj[key] = { path: route.path, children: route.children ? convertRoutes(route.children) : ({} as never) }; } } return obj; }
完整可运行代码
// 基础路由配置类型 type RouteConfig = { path: string; children?: Record<string, RouteConfig>; }; // 转换后的目标类型(修复递归终止逻辑) type Routes<T extends Record<string, RouteConfig>> = { [K in keyof T]: { path: string; children: T[K]['children'] extends Record<string, RouteConfig> ? Routes<T[K]['children']> : never; }; }; // 转换函数(修复对象初始化类型问题) function convertRoutes<T extends Record<string, RouteConfig>>(routes: T): Routes<T> { const obj = {} as Routes<T>; for (const key in routes) { if (Object.prototype.hasOwnProperty.call(routes, key)) { const route = routes[key]; obj[key] = { path: route.path, children: route.children ? convertRoutes(route.children) : ({} as never) }; } } return obj; } // 示例用法 const appRoutes = { home: { path: '/' }, about: { path: '/about', children: { team: { path: '/team' } } } }; const convertedRoutes = convertRoutes(appRoutes); // 类型检查正常:convertedRoutes.about.children.team.path 会被正确推断为 string 类型
核心优化点
- 递归类型通过
T[K]['children'] extends Record<string, RouteConfig>做条件判断,当children为undefined时,自动终止递归 - 使用
as Routes<T>对空对象做类型断言,同时通过遍历动态填充属性,确保最终返回值完全符合目标类型 - 处理
children为undefined时,用({} as never)匹配类型要求,避免类型不兼容报错
内容的提问来源于stack exchange,提问作者Johannes
相关产品推荐
相关产品推荐

