英文:
How to prevent enum values collision in Typescript
问题
我在我的应用程序中有大量的枚举,其中一些枚举具有相同的值(尽管来自不同的枚举类型)。我还有一个通用函数,它接受任何这些枚举并尝试基于枚举类型和值来描述/处理。问题在于如果键相同,枚举值会重复。
有没有办法解决这个问题?我在下面说明了问题:
enum Car {
bmw = "bmw",
ferrari = "ferrari"
}
enum Brand {
bmw = "bmw",
ferrari = "ferrari"
}
function describeIt(it: string) {
switch (it) {
case Car.bmw:
console.log("It's a BMW car");
break
case Brand.bmw:
console.log("It's the BMW brand");
break
case Car.ferrari:
console.log("It's a Ferrari car");
break
case Brand.ferrari:
console.log("It's the Ferrari brand");
break
}
}
describeIt(Car.bmw) // 得到: It's a BMW car, 正确!
describeIt(Brand.bmw) // 得到: It's a BMW car, 期望: It's the BMW brand
在Typescript Playground上试一试。
我有90%的把握,如果我问“如何生成跨所有枚举值的唯一值?”会成为一个XY问题,所以我问“如何防止枚举值冲突?”这才是核心问题。
如果可能的话,我希望保留简单的语法,我知道我可以像这样在枚举类型之前添加前缀:
enum Brand {
bmw = "Brand.bmw",
ferrari = "Brand.ferrari"
}
但这不是我要找的解决方案,我希望解决方案来自枚举类型本身。
我已经尝试搜索了很多问题,但没有一个问题解决了这个特定的问题。
英文:
I have a plenty of enums across my application, and some of these enums have the same values (although from different enum types). I also have a generic function that takes any of these enums and try to describe/handle based on the enum type and value. The problem is that the enum values are duplicated if the key is the same.
Is there some way to fix this issue? I've illustrated the problem below:
enum Car {
bmw = "bmw",
ferrari = "ferrari"
}
enum Brand {
bmw = "bmw",
ferrari = "ferrari"
}
function describeIt(it: string) {
switch (it) {
case Car.bmw:
console.log("It's a BMW car");
break
case Brand.bmw:
console.log("It's the BMW brand");
break
case Car.ferrari:
console.log("It's a Ferrari car");
break
case Brand.ferrari:
console.log("It's the Ferrari brand");
break
}
}
describeIt(Car.bmw) // Got: It's a BMW car, correct!
describeIt(Brand.bmw) // Got: It's a BMW car, expected: It's the BMW brand
Try it at the Typescript Playground.
I'm 90% sure this would be a X Y problem if I asked "How to generate a unique value across all enum values?" so instead I'm asking "How to prevent enum values collision?" that is the core problem here.
I would like to keep the simple syntax if possible, I know that I can prepend the enum type like this, for instance:
enum Brand {
bmw = "Brand.bmw",
ferrari = "Brand.ferrari"
}
But this is not what I'm looking for, I want a solution that comes from the enum type itself.
I've tried searching across many questions but none of them addresses this specific problem.
答案1
得分: 1
switch
语句根据 严格相等性 比较算法(参考:MDN,ECMAScript 规范)将表达式与 case 值进行匹配。
因此,相同字符串的字符串枚举值将始终被视为相等(即使在不同的枚举中),并且具有相同字符串值的第一个 case 将始终匹配。
为了区分值并仍然使用 "enum" 习惯用法,您可以使用一个包含永远不会相等的值的对象(例如对象、符号等) — TypeScript 手册实际上建议使用此对象模式:
对象 vs 枚举
在现代 TypeScript 中,当对象与
as const
配合使用时,您可能不需要枚举:...
相对于 TypeScript 的
enum
,这种格式的最大优点在于它使您的代码库与 JavaScript 的状态保持一致,而且 如果 枚举被添加到 JavaScript 中,您可以切换到额外的语法。
这是一个使用符号值的示例:
type Values<T> = T[keyof T];
const Car = {
Bmw: Symbol("bmw"),
Ferrari: Symbol("ferrari"),
} as const;
type Car = Values<typeof Car>;
const Brand = {
Bmw: Symbol("bmw"),
Ferrari: Symbol("ferrari"),
} as const;
type Brand = Values<typeof Brand>;
function describeIt(it: Car | Brand): void {
switch (it) {
case Car.Bmw:
console.log("这是宝马汽车");
break;
case Brand.Bmw:
console.log("这是宝马品牌");
break;
case Car.Ferrari:
console.log("这是法拉利汽车");
break;
case Brand.Ferrari:
console.log("这是法拉利品牌");
break;
}
}
describeIt(Car.Bmw); // "这是宝马汽车"
describeIt(Brand.Bmw); // "这是宝马品牌"
console.log(Car.Bmw === Car.Bmw); // true
console.log(Car.Bmw === Brand.Bmw); // false
如果出于某种原因,您需要可以序列化为字符串的枚举值,您可以创建一个具有自定义 toString
和 toJSON
方法的对象类型,如下所示:
英文:
switch
statements match expressions against case values according to the strict equality comparison algorithm (ref: MDN, ECMAScript spec).
Because of this, string enum values that are the same string will always compare as equal (even if in different enums) — and the first case with the same string value will always match.
In order to discriminate values and still use the "enum" idiom, you can use an object with values which will never compare as equal (e.g. objects, symbols, etc.) — the TypeScript handbook actually suggests this object pattern:
> ## Objects vs Enums
>
> In modern TypeScript, you may not need an enum when an object with as const
could suffice:
>
> ...
>
> The biggest argument in favour of this format over TypeScript’s enum
is that it keeps your codebase aligned with the state of JavaScript, and when/if enums are added to JavaScript then you can move to the additional syntax.
Here's an example using symbol values:
type Values<T> = T[keyof T];
const Car = {
Bmw: Symbol("bmw"),
Ferrari: Symbol("ferrari"),
} as const;
type Car = Values<typeof Car>;
const Brand = {
Bmw: Symbol("bmw"),
Ferrari: Symbol("ferrari"),
} as const;
type Brand = Values<typeof Brand>;
function describeIt(it: Car | Brand): void {
switch (it) {
case Car.Bmw:
console.log("It's a BMW car");
break;
case Brand.Bmw:
console.log("It's the BMW brand");
break;
case Car.Ferrari:
console.log("It's a Ferrari car");
break;
case Brand.Ferrari:
console.log("It's the Ferrari brand");
break;
}
}
describeIt(Car.Bmw); // "It's a BMW car"
describeIt(Brand.Bmw); // "It's the BMW brand"
console.log(Car.Bmw === Car.Bmw); // true
console.log(Car.Bmw === Brand.Bmw); // false
And if, for some reason, you need enum values which can be serialized as strings, you could create an object type with customized toString
and toJSON
methods like this:
class UniqueString {
constructor(public value: string) {}
toString(): string { return this.value; }
toJSON(): string { return this.value; }
}
type Values<T> = T[keyof T];
const Car = {
Bmw: new UniqueString("bmw"),
Ferrari: new UniqueString("ferrari"),
} as const;
type Car = Values<typeof Car>;
const Brand = {
Bmw: new UniqueString("bmw"),
Ferrari: new UniqueString("ferrari"),
} as const;
type Brand = Values<typeof Brand>;
// ...etc. (same as previous example)
console.log(String(Car.Bmw)); // "bmw"
console.log(JSON.stringify(Car.Bmw)); // '"bmw"'
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论