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

如何为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('/')的所有合法组合。相比之下,泛型递归拼接的方式更精确,能严格约束只能使用枚举值进行拼接,避免非法字符串。

六、高级类型技巧总结

  1. 枚举值提取:通过${EnumName}将枚举转换为其字符串值的联合类型,是处理枚举的常用技巧。
  2. 递归泛型:利用infer关键字递归解构数组,模拟字符串拼接逻辑,实现动态join类型。
  3. 映射类型:遍历对象键,将每个键的类型映射为对应的值类型,确保键值对完全符合约束。
  4. 类型约束:通过extends关键字限制泛型参数只能是枚举值组成的数组,避免非法输入。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 18:13:14