Typescript中的枚举(Enums)中的扩展或继承。

huangapple go评论60阅读模式
英文:

Typescript Extention or Inheritance with Enums (Enumerations)

问题

我在思考相互依赖的枚举,以及是否存在一种不需要将枚举与另一种类型结构粘合在一起的模式。

枚举 FruitEnum {
    Apple = "苹果",
    Orange = "橙子"
}

// DesertEnum 继承自 FruitEnum
枚举 DesertEnum {
    Apple = FruitEnum.Apple,
    Orange = FruitEnum.Orange,
    CheeseCake = "芝士蛋糕",
    ChocolateCake = "巧克力蛋糕",
    Pie = "馅饼"
}

// CakeEnum 继承自 DesertsEnum
枚举 CakeEnum {
    CheeseCake = DesertEnum.CheeseCake,
    ChocolateCake = DesertEnum.ChocolateCake
}

/*
    类型 'DesertEnum.Pie | CakeEnum' 无法赋值给类型 'DesertsEnum'。
    类型 'CakeEnum.CheeseCake' 无法赋值给类型 'DesertsEnum'。(2322)
*/
const whichCake: DesertEnum = Object.values(CakeEnum)
                                    .find(cake => cake === CakeEnum.ChocolateCake) ?? DesertEnum.Pie

/*
    类型 'FruitEnum | DesertsEnum.Pie' 无法赋值给类型 'DesertsEnum'。
    类型 'FruitEnum.Apple' 无法赋值给类型 'DesertsEnum'。(2322)
*/
const whichFruit: DesertEnum = Object.values(FruitEnum)
                                     .find(fruit => fruit === FruitEnum.Apple) ?? DesertEnum.Pie
// 这感觉像是一个技巧,至少应该由其中一个模式推断出来。
// 如果我们要添加一种新类型的甜点,将需要创建枚举并将其添加到 DesertsType 结构中。

type DesertsType = FruitEnum | CakeEnum | DesertEnum
英文:

I was wondering about enumerations that depend on each other, and if there is a pattern that doesn't involve gluing enumerations together with another type structure.

enum FruitEnum {
    Apple = "APPLE",
    Orange = "ORANGE"
}

// DesertEnum extends FruitEnum
enum DesertEnum {
    Apple = FruitEnum.Apple,
    Orange = FruitEnum.Orange,
    CheeseCake = "CHEESE_CAKE",
    ChocolateCake = "CHOCOLATE_CAKE",
    Pie = "PIE"
}

// CakeEnum inherits from DesertsEnum
enum CakeEnum {
    CheeseCake = DesertEnum.CheeseCake,
    ChocolateCake = DesertEnum.ChocolateCake
}

/*
    Type 'DesertEnum.Pie | CakeEnum' is not assignable to type 'DesertsEnum'.
    Type 'CakeEnum.CheeseCake' is not assignable to type 'DesertsEnum'.(2322)
*/
const whichCake: DesertEnum = Object.values(CakeEnum)
                                    .find(cake => cake === CakeEnum.ChocolateCake) ?? DesertEnum.Pie

/*
    Type 'FruitEnum | DesertsEnum.Pie' is not assignable to type 'DesertsEnum'.
    Type 'FruitEnum.Apple' is not assignable to type 'DesertsEnum'.(2322)
*/
const whichFruit: DesertEnum = Object.values(FruitEnum)
                                     .find(fruit => fruit === FruitEnum.Apple) ?? DesertEnum.Pie

I tried using types that combine everything, but it just seams like extra code that is error prone.

// This feels like a hack, it should be inferred by at least one of the patterns.
// If we were to add a new type of desert, it would be necessary to both create 
// the enumeration and add it to the DesertsType structure.

type DesertsType = FruitEnum | CakeEnum | DesertEnum

答案1

得分: 1

如果你并非特别依赖于 TypeScript 的 enum 关键字,你可以通过创建 JavaScript 对象来实现与 TypeScript 枚举几乎相同的功能,方法是创建 as const 对象,并使用相同名称的类型来为它们创建类型,使用 (typeof MyEnum)[keyof typeof MyEnum] 模式。这将创建一个作为字符串联合的类型,使得 TypeScript 能够理解这种交叉操作。

与使用 TypeScript 枚举相比,使用 JavaScript 对象有一些优点,尽管并非全部都是积极的。

+ 语法对于新接触 TypeScript 的 JavaScript 开发者更加熟悉

+ 生成的 JavaScript 更加清晰,因为只删除了 TypeScript 的注解

+ TypeScript 不会为数字枚举生成反向查找,这可以避免令人困惑和意外的问题

- TypeScript 不会为枚举生成相关联的类型,因此你需要自己创建,使得代码略显繁琐

- 因为该类型被构造为常量类型的联合(例如 'a' | 'b'),在你的 IDE 中可能无法获得如此有用的自动补全,因为它会直接建议这些值,而不是建议通过枚举对象访问它们


在我看来,优点大于缺点,所以我在自己的工作中采用了这种方法,不过这些差异很小,主要取决于代码风格或个人偏好。我认为你触及到了其中极少数实际上会有所不同的情况之一。

以下是你的示例,使用自定义枚举并配合实用的 EnumTypeOf 类型,以使自定义类型的构建更加明显:

type EnumTypeOf<T extends Record<string, unknown>> = T[keyof T];

const FruitEnum = {
    Apple: "APPLE",
    Orange: "ORANGE"
} as const;
type FruitEnum = EnumTypeOf<typeof FruitEnum>;

// DesertEnum 继承 FruitEnum
const DesertEnum = {
    Apple: FruitEnum.Apple,
    Orange: FruitEnum.Orange,
    CheeseCake: "CHEESE_CAKE",
    ChocolateCake: "CHOCOLATE_CAKE",
    Pie: "PIE"
} as const;
type DesertEnum = EnumTypeOf<typeof DesertEnum>;

// CakeEnum 继承 DesertsEnum
const CakeEnum = {
    CheeseCake: DesertEnum.CheeseCake,
    ChocolateCake: DesertEnum.ChocolateCake
} as const;
type CakeEnum = EnumTypeOf<typeof CakeEnum>;


const whichCake: DesertEnum = (Object.values(CakeEnum))
                                    .find((cake) => cake === CakeEnum.ChocolateCake) ?? DesertEnum.Pie;

const whichFruit: DesertEnum = Object.values(FruitEnum)
                                     .find((fruit) => fruit === FruitEnum.Apple) ?? DesertEnum.Pie;

TypeScript Playground

英文:

If you're not tied to using TypeScript's enum keyword specifically, you can create JavaScript objects that act practically identically to TypeScript enums by creating as const objects, and creating types for them with the same name using the (typeof MyEnum)[keyof typeof MyEnum] pattern. This creates the type as a string union, which allows TypeScript to figure out this sort of crossover.

Using JavaScript objects instead of TypeScript enums has a couple of advantages, though it's not all positive.

+ The syntax is more familiar to JavaScript developers who are new to TypeScript

+ The generated JavaScript is more obvious, because only TypeScript annotations are removed

+ TypeScript won't generate reverse lookups for number enums, which can be unintuitive and cause unexpected problems

- TypeScript won't generate the type associated with an enum for you, so you need to create it yourself, making the code a bit more verbose

- Because the type is constructed as a union of constant types (e.g. &#39;a&#39; | &#39;b&#39;), you may not get as useful autocomplete in your IDE because it will suggest those values directly rather than suggesting you access them through your enum object


In my opinion the upsides outweigh the downsides, so I use this approach in my own work, but also the differences are minor enough that it comes largely down to code style or personal preference. I think you've hit on one of the few instances where it actually makes a difference.

Here's your example, using custom enums with a utility EnumTypeOf type to make the custom type construction a bit more obvious:

type EnumTypeOf&lt;T extends Record&lt;string, unknown&gt;&gt; = T[keyof T];

const FruitEnum = {
    Apple: &quot;APPLE&quot;,
    Orange: &quot;ORANGE&quot;
} as const;
type FruitEnum = EnumTypeOf&lt;typeof FruitEnum&gt;;

// DesertEnum extends FruitEnum
const DesertEnum = {
    Apple: FruitEnum.Apple,
    Orange: FruitEnum.Orange,
    CheeseCake: &quot;CHEESE_CAKE&quot;,
    ChocolateCake: &quot;CHOCOLATE_CAKE&quot;,
    Pie: &quot;PIE&quot;
} as const;
type DesertEnum = EnumTypeOf&lt;typeof DesertEnum&gt;;

// CakeEnum inherits from DesertsEnum
const CakeEnum = {
    CheeseCake: DesertEnum.CheeseCake,
    ChocolateCake: DesertEnum.ChocolateCake
} as const;
type CakeEnum = EnumTypeOf&lt;typeof CakeEnum&gt;;


const whichCake: DesertEnum = (Object.values(CakeEnum))
                                    .find((cake) =&gt; cake === CakeEnum.ChocolateCake) ?? DesertEnum.Pie;

const whichFruit: DesertEnum = Object.values(FruitEnum)
                                     .find((fruit) =&gt; fruit === FruitEnum.Apple) ?? DesertEnum.Pie;

TypeScript Playground

huangapple
  • 本文由 发表于 2023年7月4日 19:59:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/76612408.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定