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

如何在TypeScript类型解析阶段确保makeEnum工具函数接收的枚举对象无重复值?

如何在TypeScript类型解析阶段确保makeEnum工具函数接收的枚举对象无重复值?

我明白你的需求——你现在的makeEnum工具函数已经能很好地生成枚举的映射、值数组和类型,但你希望TypeScript在编译阶段就直接标记出传入对象里的重复值,避免运行时才发现问题。这个需求完全可以通过TypeScript的高级类型约束来实现,我来给你一步步拆解解决方案:

首先,先回顾一下你当前的makeEnum实现(方便对照):

const makeEnum = <const O extends Record<string, string>>(obj: O) => {
  return {
    map: obj,
    values: (Object.keys(obj) as (keyof O)[]).map(k => obj[k]) as readonly O[keyof O][],
    type: null as unknown as O[keyof O],
  } as const
}

// 现有用法示例
const example = makeEnum({ Dead: "Beef", Hello: "World", Lorum: "Ipsum" })
export const Example = example.map
export const ExampleValues = example.values
export type ExampleType = typeof example.type

现在的问题是,当你传入有重复值的对象时,TypeScript会把值合并成联合类型,掩盖了重复。我们需要给makeEnum加上一个类型约束,强制要求传入的对象值必须唯一。

核心实现:用条件类型检测重复值

我们可以先写一个辅助类型NoDuplicateValues,它的作用是检查对象的所有值是否唯一,如果有重复就触发类型错误:

// 辅助类型:检查对象的所有值是否唯一
type NoDuplicateValues<T extends Record<string, string>> = {
  [K in keyof T]: T[K] extends T[Exclude<keyof T, K>] ? never : T[K]
} extends { [K in keyof T]: T[K] } ? T : never;

这个类型的工作逻辑是:

  1. 遍历对象的每个键K,检查当前键对应的值T[K]是否出现在其他所有键的值中(通过Exclude<keyof T, K>排除当前键,取剩下键的值的联合类型)
  2. 如果某个值重复了,就把该键的类型设为never
  3. 最后比较修改后的类型和原类型是否完全一致:如果一致,说明没有重复值,返回原类型;否则返回never(让TypeScript标记为错误)

接下来,我们只需要修改makeEnum的泛型约束,把O的范围限制为满足NoDuplicateValues<O>的对象:

const makeEnum = <const O extends Record<string, string> & NoDuplicateValues<O>>(obj: O) => {
  return {
    map: obj,
    values: (Object.keys(obj) as (keyof O)[]).map(k => obj[k]) as readonly O[keyof O][],
    type: null as unknown as O[keyof O],
  } as const
}

测试效果

现在当你传入有重复值的对象时,TypeScript会直接在编译阶段标记错误:

// 这个例子里Sneaky的值和Dead重复了,TypeScript会直接报错
const example = makeEnum({
  Dead: "Beef",
  Hello: "World",
  Lorum: "Ipsum",
  Sneaky: "Beef" // ❌ TypeScript会提示类型不兼容
})

而没有重复值的对象依然可以正常使用,并且原来的所有功能都保留:

const validExample = makeEnum({ Dead: "Beef", Hello: "World", Lorum: "Ipsum" })

export const Example = validExample.map // 类型:readonly { Dead: "Beef"; Hello: "World"; Lorum: "Ipsum"; }
export const ExampleValues = validExample.values // 类型:readonly ("Beef" | "World" | "Ipsum")[]
export type ExampleType = typeof validExample.type // 类型:"Beef" | "World" | "Ipsum"

一些注意事项

  • 这个实现目前针对的是字符串值的重复检测,如果需要支持数字、布尔等其他原始类型,只需要把Record<string, string>改成Record<string, string | number | boolean>即可;
  • 这种检测只在编译阶段生效,完全不会影响运行时性能;
  • 仅对字面量对象有效,如果传入的是动态生成的对象(比如从变量中传入),TypeScript可能无法正确推导类型,也就无法检测重复。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 08:42:58