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

Dav*_*eel 4 string union enums typescript

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

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

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

Run Code Online (Sandbox Code Playgroud)

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

type ColorShape = Color | Shape;
const test: ColorShape = ColorShape.RED;
Run Code Online (Sandbox Code Playgroud)

我收到编译时错误:

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

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

jca*_*alz 6

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

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

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

// types
type Baz = { a: number };
interface Qux { b: string }
Run Code Online (Sandbox Code Playgroud)

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

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

// separate namespaces
const Xyz = { z: 1 };
type Xyz = { y: string };
Run Code Online (Sandbox Code Playgroud)

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


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

// both
class Abc { c = "" }
enum Def { D = "e" }
Run Code Online (Sandbox Code Playgroud)

这里的名称Abc既指可以在运行时引用的类构造函数new Abc(),也指可以在编译时用来讨论类实例的类实例类型const instance: Abc = ...,例如。如果你写的话,const instance: Abc = new Abc()你首先指的是类型,然后是值。

同样,该名称Def既指可以在运行时引用的枚举对象Def.D === "e"(如),也指枚举类型它是枚举成员值的类型的并集(如 )const def: Def = ...。如果你写的话,const def: Def = Def.D你首先指的是类型,然后是值。


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

enum Color {
    RED = 'red',
    BLUE = 'blue',
}
Run Code Online (Sandbox Code Playgroud)

这或多或少会编译成类似的东西

const Color = {
  RED: "red",
  BLUE: "blue"
}
Run Code Online (Sandbox Code Playgroud)

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

type Color = "red" | "blue";
Run Code Online (Sandbox Code Playgroud)

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


现在,当你写

type ColorShape = Color | Shape;
Run Code Online (Sandbox Code Playgroud)

您正在创建一个ColorShape 类型。但type ColorShape = ...不是enum,因此您所做的就是创建类型。这里没有相应的价值产生。在创建类似组合的东西方面,您只完成了一半的工作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
Run Code Online (Sandbox Code Playgroud)

或者你可以使用对象传播

const ColorShape = { ...Color, ...Shape };
/* const ColorShape: {
    CIRCLE: Shape.CIRCLE;
    SQUARE: Shape.SQUARE;
    RED: Color.RED;
    BLUE: Color.BLUE;
} */
Run Code Online (Sandbox Code Playgroud)

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

const test: ColorShape = ColorShape.RED; // okay
Run Code Online (Sandbox Code Playgroud)

请注意,我说的是“大部分”。该enum声明还将更多类型纳入范围;它实际上充当namespace您可以使用枚举值的点路径作为类型本身的地方:

interface RedState {
    name: string;
    color: Color.RED // <-- this is a type
}
Run Code Online (Sandbox Code Playgroud)

但你的任何一个定义都不会发生这种情况ColorShape

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

你可能不关心这个。如果您这样做,那么我知道如何执行此操作的唯一方法是手动创建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;
}
Run Code Online (Sandbox Code Playgroud)

不太好,可能也不是你需要的东西。但为了完整起见,我想我只是表明 s 的幕后还有更多的事情发生enum

Playground 代码链接