如何为TypeScript Enum数组join('/')生成的Angular路由常量定义类型?
问题描述
我定义了如下TypeScript枚举:
export enum SampleEnum { ID = 'ID', PERSON = 'PERSON', }
以及常量:
export const SampleConst = { someValue1: [SampleEnum.PERSON, SampleEnum.ID].join('/'), someValue2: [SampleEnum.PERSON].join('/'), }
请问如何为SampleConst定义类似SampleEnum[].join('/')的类型?是否可以实现?或者是否可以使用正则类型?我希望找到合适的映射类型或泛型类型来解决该问题,也想了解相关的高级类型技巧。我曾尝试获取枚举的所有子集,但效果不佳。
补充场景(Angular路由)
实际我是在Angular路由场景中使用该方案,目前代码如下:
enum ERoutesName { EMPTY = 'EMPTY', AUTH = 'AUTH', AUTH_PHONE = 'AUTH_PHONE', // ... 其他路由名称 } export enum ERoutesParts { ID = ':id', EMPTY = '', AUTH = 'auth', AUTH_PHONE = 'auth-phone', // ... 其他路由片段 } export const EPartialRoutes: /* 需要定义合适的类型 */ = { [ERoutesName.EMPTY]: [ERoutesParts.EMPTY].join('/'), [ERoutesName.AUTH]: [ERoutesParts.AUTH].join('/'), [ERoutesName.AUTH_PHONE]: [ERoutesParts.AUTH_PHONE].join('/'), // ... 其他路由映射 };
需要为EPartialRoutes定义合适的类型。
解决方案与高级类型技巧
一、核心思路:模板字面量类型 + 泛型模拟join('/')逻辑
TypeScript 4.1+支持的模板字面量类型可以精准模拟字符串拼接,结合泛型递归就能实现数组join('/')的类型效果,再配合映射类型可给对象定义精确的键值约束。
二、针对SampleConst的类型定义
先提取枚举的字符串值类型,再用泛型递归实现拼接逻辑:
// 提取SampleEnum的字符串值联合类型 type SampleEnumValues = `${SampleEnum}`; // 泛型:递归模拟数组元素用/连接的类型 type JoinedWithSlash<T extends SampleEnumValues[]> = T extends [infer First, ...infer Rest] ? Rest extends SampleEnumValues[] ? `${First}${Rest extends [] ? '' : `/${JoinedWithSlash<Rest>}`}` : never : ''; // 给SampleConst定义精确类型 type SampleConstType = { someValue1: JoinedWithSlash<[SampleEnum.PERSON, SampleEnum.ID]>; someValue2: JoinedWithSlash<[SampleEnum.PERSON]>; }; // 验证:类型完全匹配 export const SampleConst: SampleConstType = { someValue1: [SampleEnum.PERSON, SampleEnum.ID].join('/'), someValue2: [SampleEnum.PERSON].join('/'), };
三、针对Angular路由EPartialRoutes的类型定义
通过映射类型绑定路由名称与路由片段的对应关系,再生成拼接后的字符串类型:
// 提取ERoutesParts的字符串值联合类型 type ERoutesPartsValues = `${ERoutesParts}`; // 定义路由名称到路由片段数组的映射(确保键值对应正确) type RouteNameToPart = { [ERoutesName.EMPTY]: [ERoutesParts.EMPTY]; [ERoutesName.AUTH]: [ERoutesParts.AUTH]; [ERoutesName.AUTH_PHONE]: [ERoutesParts.AUTH_PHONE]; // ... 补充其他路由的对应关系 }; // 泛型:将路由片段数组转换为/连接的字符串类型 type JoinRouteParts<T extends ERoutesPartsValues[]> = T extends [infer First, ...infer Rest] ? `${First}${Rest extends [] ? '' : `/${JoinRouteParts<Rest>}`}` : ''; // 最终的EPartialRoutes类型:映射每个路由名称到对应的拼接字符串 type EPartialRoutesType = { [K in keyof RouteNameToPart]: JoinRouteParts<RouteNameToPart[K]>; }; // 验证:类型完全匹配,新增路由只需更新RouteNameToPart export const EPartialRoutes: EPartialRoutesType = { [ERoutesName.EMPTY]: [ERoutesParts.EMPTY].join('/'), [ERoutesName.AUTH]: [ERoutesParts.AUTH].join('/'), [ERoutesName.AUTH_PHONE]: [ERoutesParts.AUTH_PHONE].join('/'), // ... 其他路由映射 };
四、进阶:支持多片段路由
如果需要多片段路由(比如auth/:id),只需在RouteNameToPart中定义对应数组即可,JoinRouteParts泛型会自动处理:
type RouteNameToPart = { // ... 原有路由 [ERoutesName.AUTH_DETAIL]: [ERoutesParts.AUTH, ERoutesParts.ID]; }; // 对应EPartialRoutes中的字段会自动推导为"auth/:id"类型 [ERoutesName.AUTH_DETAIL]: [ERoutesParts.AUTH, ERoutesParts.ID].join('/'),
五、关于正则类型的说明
TypeScript支持正则字面量类型,但无法直接用正则匹配SampleEnum[].join('/')的所有合法组合。相比之下,泛型递归拼接的方式更精确,能严格约束只能使用枚举值进行拼接,避免非法字符串。
六、高级类型技巧总结
- 枚举值提取:通过
${EnumName}将枚举转换为其字符串值的联合类型,是处理枚举的常用技巧。 - 递归泛型:利用
infer关键字递归解构数组,模拟字符串拼接逻辑,实现动态join类型。 - 映射类型:遍历对象键,将每个键的类型映射为对应的值类型,确保键值对完全符合约束。
- 类型约束:通过
extends关键字限制泛型参数只能是枚举值组成的数组,避免非法输入。
内容的提问来源于stack exchange,提问作者Alex Smykov
相关产品推荐
相关产品推荐

