在打字稿中,为什么枚举的联合不是枚举?

发布于 2025-01-09 20:03:12 字数 402 浏览 0 评论 0原文

我有两个想要联合的枚举。

enum Color {
    RED = 'red',
    BLUE = 'blue',
}

enum Shape {
    CIRCLE = 'circle',
    SQUARE = 'square',
}

但是,当我声明联合类型并尝试引用成员时:

type ColorShape = Color | Shape;
const test: ColorShape = ColorShape.RED;

我收到编译时错误:

TS2693:“ColorShape”仅指一种类型,但在此处用作值。

这是为什么呢?枚举的联合本身不也是一个枚举吗?

I have two enums that I would like to union.

enum Color {
    RED = 'red',
    BLUE = 'blue',
}

enum Shape {
    CIRCLE = 'circle',
    SQUARE = 'square',
}

However when I declare a union type and attempt to reference a member:

type ColorShape = Color | Shape;
const test: ColorShape = ColorShape.RED;

I get a compile time error:

TS2693: 'ColorShape' only refers to a type, but is being used as a value here.

Why is this? Would an union of enums not also be an enum itself?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

内心旳酸楚 2025-01-16 20:03:12

重要的是要记住 TypeScript 之间的 TypeScript 类型之间的差异,这些差异在编译时就存在,并且是 从发出的 JavaScript 代码中删除;和 JavaScript ,它们也存在于运行时并出现在发出的 JavaScript 代码中。 TypeScript 尝试跟踪值和类型,但 JavaScript 只知道值。

大多数时候,当您在 TypeScript 中进行声明时,您要么将一个值带入作用域,要么将一个类型带入作用域,但不能同时带入两者:

// values
const foo = 123;
function bar() { }

// types
type Baz = { a: number };
interface Qux { b: string }

在上面,foobar 是存在于发出的 JavaScript 中的值,而 BazQux 是被删除的类型。

请注意,值和类型具有完全不同的命名空间;您可以拥有同名的值和类型,并且它们不必彼此相关。编译器不会混淆它们:

// separate namespaces
const Xyz = { z: 1 };
type Xyz = { y: string };

这里 Xyz 是值的名称(具有 z 属性且值为 1 的对象),并且也是类型的名称(具有 y 属性且值类型为 string 的对象类型)。这些是无关的。如果我编写 const uvw = Xyz;,那么我正在编写使其成为 JavaScript 的值级代码,因此 Xyz 就是值。如果我编写 interface Rst extends Xyz { } ,那么我正在编写从 JavaScript 中删除的类型级代码,因此 Xyz 就是类型。


好的,所以大多数时候,当您声明某个命名事物时,该事物要么是类型,要么是值,但不能同时是两者。但是有两个声明确实产生了同名的值和类型: class 声明枚举声明

// both
class Abc { c = "" }
enum Def { D = "e" }

这里名称 Abc 既指您可以在运行时引用的类构造函数 value,如 new Abc(),它还指一个类您可以在编译时使用实例类型来讨论类实例,例如const实例:Abc = ...。如果您编写const instance: Abc = new Abc(),您将首先引用类型,然后引用值。

同样,名称 Def 指的是您可以在运行时引用的枚举对象 value,例如 Def.D === "e",它还引用一个枚举类型,它是枚举成员值类型的联合,例如const def:Def = ...。如果您编写 const def: Def = Def.D,您将首先引用类型,然后引用值。


现在,虽然 class 是 JavaScript 的一部分(自 ES2015 起),但 enum 不是。因此,当您编写 enum 时,编译器基本上会 将其降级为一些 JavaScript。让我们看看您的 Color 枚举:

enum Color {
    RED = 'red',
    BLUE = 'blue',
}

这或多或少会编译成一些行为就像

const Color = {
  RED: "red",
  BLUE: "blue"
}

它只是一个普通对象。同时, Color 类型的行为类似于

type Color = "red" | "blue";

这并不完全正确,因为 enum 已被“标记”,因此您不能只为它们分配一个字符串,但它很接近足以满足我们的目的。因此,当您的 enum Color 声明类似于上述值和类型声明的组合时。


现在,当您编写时,

type ColorShape = Color | Shape;

您正在创建一个 ColorShape type。但 type ColorShape = ... 不是 enum,因此您所做的就是创建类型。这里没有相应的价值产生。您只完成了创建类似枚举的组合事物的一半工作。如果您想要相应的值,则必须手动创建它。

您想要的是看起来像 {RED: "red", BLUE: "blue", CIRCLE: "circle", SQUARE: "square"} 的东西。有多种方法可以获取 Color 值和 Shape 值并创建这样的对象。

您可以使用 Object.assign( ) 方法

const ColorShape = Object.assign({}, Color, Shape);
// const ColorShape: typeof Color & typeof Shape

或者你可以使用 对象传播

const ColorShape = { ...Color, ...Shape };
/* const ColorShape: {
    CIRCLE: Shape.CIRCLE;
    SQUARE: Shape.SQUARE;
    RED: Color.RED;
    BLUE: Color.BLUE;
} */

无论哪种方式都应该有效。完成此操作后,现在,您(大部分)可以使用 ColorShape,就好像它是由 Color 组成的组合enumShape

const test: ColorShape = ColorShape.RED; // okay

请注意,我说的是“大部分”。 enum 声明还将更多类型引入范围;它实际上充当 namespace ,您可以在其中使用枚举值的虚线路径本身就是类型:

interface RedState {
    name: string;
    color: Color.RED // <-- this is a type
}

但是您的 ColorShape 定义中的任何一个都不会发生这种情况:

interface RedState {
    name: string;
    color: ColorShape.RED // error!
    // 'ColorShape' only refers to a type, but is being used as a namespace here.
}

您可能不关心这一点。如果您这样做,那么我知道如何执行此操作的唯一方法是手动创建您的 ColorShape 命名空间并从中导出每种类型:

namespace ColorShape {
    export type CIRCLE = typeof ColorShape.CIRCLE;
    export type SQUARE = typeof ColorShape.SQUARE;
    export type RED = typeof ColorShape.RED;
    export type BLUE = typeof ColorShape.BLUE;
}

不太好,可能不是您需要的任何内容。但为了完整起见,我想我只是展示 enum 的幕后还有更多的事情发生。

<一href="https://www.typescriptlang.org/play?#code/PTAEDcEMBsFcFMDOBYAUAYwPYDtEBdQAzTTUAXlAEYAmAZgG41DZt08BLHUAI0gCcA FAEpQAb1ABfNGhCg8ATwaOSNAuWgAQpABe5MaEgAuUNlgBbbvD6TGqdtjxXCkdPFABFWAA993Y-j57AHNJaVRZRHhFfkhHE0gzJGjXFAwcfFAADXld CnFtY0obVSU3bNz9eX88QOwQiVsw2W5MPAALNHRoSEREUABBbnR9YYoAIjHQ1HhTM1AAEXhCfXm9MfhJqVQwmfNQAGFMaExrUTRQC9AAJQBRVYoAcj 54ABMHgBpzy40AGQBVG56B7cODwD5oLZoXZzADKbUg6jOqEuBwAkld9j9AY90Ow+F0wZ9kZcYe4-v1bkDEABHWD8QkQsJqNyHY58OEItwUVknUAAH1 AHOUjXCACpOukCDz2fD1BQAPLcABW8DYADoeoh2EFsAJRBJ3gcjidDUL4EJbLIsLgpcaZZzjMzMMtpaAAGRyUrOwWy+BoUXAMLWjLSs16cRqyPSw2R tVhhoyUWgYO2tlm4xIlH7dGYm7GM1q7MYrG2FGk8m3fO+unkik3UuXW7zYzStVNhsXX4Alt2tVd+sQ0ABoOSuRIPA9tO+vSh6vt0CyTAAa0g8jC9kc fGcrmurxheFibkzl2wCXg1VqQQ7ybtk5ObbuC7AAB4ALSvuRtdh9b8GT3KRltiA09EkQZIWTtMNjwueAvEUE4CGZNFi2xf94G9WdOULHMSy+GC4IQt DBVrSkKCdF1IOrcs62vWD4L4RDSmuR8yK9Cipyw9s8NAOjCKQ-s9HIo0OOUPt-gHVBISAjcnBcNwrj3A9HGoMRuJA89QACYJrywNk73tUSm0AsIgA" rel="noreferrer">Playground 代码链接

It is important to keep in mind the difference in TypeScript between TypeScript types, which exist at compile time and are erased from the emitted JavaScript code; and JavaScript values, which also exist at runtime and appear in the emitted JavaScript code. TypeScript tries to keep track of values and types, but JavaScript only knows about values.

Most of the time when you make a declaration in TypeScript you are either bringing a value into scope or a type into scope but not both:

// values
const foo = 123;
function bar() { }

// types
type Baz = { a: number };
interface Qux { b: string }

In the above, foo and bar are values that exist in the emitted JavaScript, while Baz and Qux are types that are erased.

And note that values and types have completely different namespaces; you can have a value and a type with the same name, and they don't have to be related to each other. The compiler doesn't confuse them:

// separate namespaces
const Xyz = { z: 1 };
type Xyz = { y: string };

Here Xyz is the name of a value (an object with a z property whose value is 1) and also the name of a type (an object type with a y property whose value type is string). These are unrelated. If I write const uvw = Xyz; then I'm writing value-level code that makes it to JavaScript and therefore the Xyz is the value. If I write interface Rst extends Xyz { } then I'm writing type-level code that's erased from JavaScript and therefore the Xyz is the type.


Okay, so most of the time when you declare some named thing, that thing is either a type or a value but not both. But there are two declarations that do bring into existence both a value and a type of the same name: the class declaration and the enum declaration:

// both
class Abc { c = "" }
enum Def { D = "e" }

Here the name Abc refers to both a class constructor value that you can reference at runtime like new Abc(), and it also refers to a class instance type that you can use at compile time to talk about class instances like const instance: Abc = .... And if you write const instance: Abc = new Abc() you are referring first to the type and then to the value.

Similarly, the name Def refers to both an enum object value that you can reference at runtime like Def.D === "e", and it also refers to an enum type that is the union of the types of the enum member values like const def: Def = .... And if you write const def: Def = Def.D you are referring first to the type and then to the value.


Now, while class is part of JavaScript (since ES2015), enum is not. So when you write an enum the compiler will essentially downlevel it to some JavaScript. Let's look at your Color enum:

enum Color {
    RED = 'red',
    BLUE = 'blue',
}

This more or less compiles to something that acts like

const Color = {
  RED: "red",
  BLUE: "blue"
}

It's just a plain object. Meanwhile the type Color is something that acts like

type Color = "red" | "blue";

That's not strictly true because enums are "marked" so that you can't just assign a string to them, but it's close enough for our purposes. So when your enum Color declaration is similar to the combination of the above value and type declarations.


Now, when you write

type ColorShape = Color | Shape;

You are creating a ColorShape type. But type ColorShape = ... is not an enum, and so all you've done is create the type. There is no corresponding value brought into existence here. You've only done half the work in creating a combined enum-like thing. If you want the corresponding value, you will have to create it manually.

What you want is something that looks like {RED: "red", BLUE: "blue", CIRCLE: "circle", SQUARE: "square"}. There are multiple ways to take the Color value and the Shape value and make such an object.

You could use the Object.assign() method:

const ColorShape = Object.assign({}, Color, Shape);
// const ColorShape: typeof Color & typeof Shape

Or you could use object spread:

const ColorShape = { ...Color, ...Shape };
/* const ColorShape: {
    CIRCLE: Shape.CIRCLE;
    SQUARE: Shape.SQUARE;
    RED: Color.RED;
    BLUE: Color.BLUE;
} */

Either way should work. Once you've done this, now, you can (mostly) use ColorShape as if it were a combined enum composed of Color and Shape:

const test: ColorShape = ColorShape.RED; // okay

Note that I said "mostly". The enum declaration also brings a few more types into scope; it actually acts as a namespace where you can use the dotted path to enum values as types themselves:

interface RedState {
    name: string;
    color: Color.RED // <-- this is a type
}

But that doesn't happen with either of your ColorShape definitions:

interface RedState {
    name: string;
    color: ColorShape.RED // error!
    // 'ColorShape' only refers to a type, but is being used as a namespace here.
}

You probably don't care about that. If somehow you do, then the only way I know how to do this is to manually create your ColorShape namespace and export each type from it:

namespace ColorShape {
    export type CIRCLE = typeof ColorShape.CIRCLE;
    export type SQUARE = typeof ColorShape.SQUARE;
    export type RED = typeof ColorShape.RED;
    export type BLUE = typeof ColorShape.BLUE;
}

Not great, and probably not anything you need. But for completeness I figured I'd just show that there's a bit more going on under the hood with enums.

Playground link to code

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文