如何在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;
这个类型的工作逻辑是:
- 遍历对象的每个键
K,检查当前键对应的值T[K]是否出现在其他所有键的值中(通过Exclude<keyof T, K>排除当前键,取剩下键的值的联合类型) - 如果某个值重复了,就把该键的类型设为
never - 最后比较修改后的类型和原类型是否完全一致:如果一致,说明没有重复值,返回原类型;否则返回
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
相关产品推荐
相关产品推荐

