TypeScript:为带const断言的路由数组创建递归函数参数类型
为带const断言的递归路由数组注入element字段的类型问题
路由数组与类型定义
我有如下结构的路由数组,注意:as const断言无法移除,因为其他代码依赖它:
const routes = [ { id: 'root', path: '/', children: [ { id: 'auth', path: 'auth', children: [ { id: 'login', path: 'login', }, { id: 'register', path: 'register', }, { id: 'resetPassword', path: 'reset-password', }, { id: 'resendConfirmation', path: 'resend-confirmation', }, ], }, { id: 'playground', path: 'playground', children: [ { id: 'formControlsPlayground', path: 'form-controls', }, ], }, ], }, ] as const;
我将该数组的类型提取为:
type Routes = typeof routes;
注入element字段的函数需求
我需要编写一个injectElement函数,为每个路由注入element字段(实际会通过路由的id从routeComponentLookup中获取对应组件,示例中用字符串代替JSX)。函数返回RouteObject类型,定义如下:
type RouteObject = { id?: string; path?: string; element?: string; children?: RouteObject[]; }
尝试编写的函数如下,但无法传入routes[0].children进行递归调用,需要为函数参数创建递归类型,且保留const断言带来的字面量类型,以便通过id索引routeComponentLookup:
// 手动定义Id类型仅为简化示例,实际会自动从路由数组生成 type Id = "root" | "auth" | "login" | "register" | "resetPassword" | "resendConfirmation" | "playground" | "formControlsPlayground" declare const routeComponentLookup: Record<Id, string>; const injectElement = (routes: Routes) => { const injectedRoutes: RouteObject[] = []; for (let route of routes) { injectedRoutes.push({ path: route.path, element: routeComponentLookup[route.id], children: route.children ? injectElement(route.children) : undefined, // 此处报错:类型不匹配 }); } return injectedRoutes; }; console.log(injectElement(routes));
解决方法
1. 定义递归路由项类型
首先定义描述单个路由项的递归类型,让TypeScript能识别嵌套的路由结构:
type RouteItem<T extends readonly any[]> = { readonly id: string; readonly path: string; readonly children?: T; }; type RecursiveRouteArray = readonly RouteItem<RecursiveRouteArray>[];
2. 调整函数参数为通用递归类型
将injectElement的参数改为通用递归类型,这样TypeScript就能正确推断route.children的类型,允许递归调用:
const injectElement = <T extends RecursiveRouteArray>(routes: T) => { const injectedRoutes: RouteObject[] = []; for (const route of routes) { injectedRoutes.push({ path: route.path, element: routeComponentLookup[route.id as Id], // 若Id自动生成可省略此断言 children: route.children ? injectElement(route.children) : undefined, }); } return injectedRoutes; };
3. 自动生成Id类型(可选优化)
如果不想手动维护Id类型,可以通过递归类型自动从路由数组中提取所有id的字面量:
type ExtractRouteIds<T extends RecursiveRouteArray> = T[number] extends infer R ? R extends RouteItem<infer C> ? R['id'] | (C extends RecursiveRouteArray ? ExtractRouteIds<C> : never) : never : never; type AutoGeneratedId = ExtractRouteIds<typeof routes>; // AutoGeneratedId 会自动推导为所有路由id的字面量联合类型 declare const routeComponentLookup: Record<AutoGeneratedId, string>;
调整后,递归调用injectElement(route.children)不再报错,同时保留了const断言带来的字面量类型安全,确保route.id能正确索引routeComponentLookup。
内容的提问来源于stack exchange,提问作者Vishal
相关产品推荐
相关产品推荐

