在打字稿中,为什么枚举的并集不是枚举?
In typescript, why is a union of enums not also an enum?
我想合并两个枚举。
enum Color {
RED = 'red',
BLUE = 'blue',
}
enum Shape {
CIRCLE = 'circle',
SQUARE = 'square',
}
然而,当我声明联合类型并尝试引用成员时:
type ColorShape = Color | Shape;
const test: ColorShape = ColorShape.RED;
我遇到编译时错误:
TS2693: 'ColorShape' only refers to a type, but is being used as a value here.
这是为什么?枚举的联合不也是枚举本身吗?
重要的是要记住 TypeScript 类型 之间的 TypeScript 差异,这些差异存在于编译时并且 erased 来自发出的 JavaScript代码;和 JavaScript values,它们也存在于运行时并出现在发出的 JavaScript 代码中。 TypeScript 试图跟踪值和类型,但 JavaScript 只知道值。
大多数情况下,当您在 TypeScript 中进行声明时,您要么将值引入范围,要么将类型引入范围,但不会同时引入两者:
// values
const foo = 123;
function bar() { }
// types
type Baz = { a: number };
interface Qux { b: string }
上面的foo
和bar
是存在于发出的JavaScript中的值,而Baz
和Qux
是被擦除的类型.
并注意值和类型具有完全不同的命名空间;您可以拥有同名的值和类型,并且它们不必相互关联。编译器不会混淆它们:
// separate namespaces
const Xyz = { z: 1 };
type Xyz = { y: string };
这里Xyz
是一个值的名字(一个z
属性的对象,它的值为1
)也是一个类型的名字(一个y
属性 的对象类型,其值类型为 string
)。这些是无关的。如果我写 const uvw = Xyz;
那么我正在写 value-level 代码,使其成为 JavaScript,因此 Xyz
是值。如果我写 interface Rst extends Xyz { }
那么我正在写 type-level 从 JavaScript 中删除的代码,因此 Xyz
是类型。
好的,所以大多数时候当你声明一些命名的东西时,那个东西要么是一个类型,要么是一个值,但不是两者。但是有两个声明确实使同名的值和类型都存在:the class
declaration and the enum
declaration:
// both
class Abc { c = "" }
enum Def { D = "e" }
这里的名称 Abc
指的是一个 class 构造函数 value ,你可以像 new Abc()
一样在运行时引用它,它也引用一个 class 实例 type ,您可以在编译时使用它来谈论 class 实例,如 const instance: Abc = ...
。如果你写 const instance: Abc = new Abc()
你首先指的是类型,然后是值。
类似地,名称 Def
既指一个枚举对象 value,你可以像 Def.D === "e"
一样在运行时引用它,它也指一个enum type 是枚举成员值类型的联合,如 const def: Def = ...
。如果你写 const def: Def = Def.D
你首先指的是类型,然后是值。
现在,虽然 class
是 JavaScript 的一部分(自 ES2015 起),但 enum
不是。因此,当您编写 enum
时,编译器实际上会将其 downlevel 变为 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 ColorShape = ...
不是 enum
,所以您所做的只是创建类型。这里没有产生相应的值。您只完成了创建类似 enum
的组合的一半工作。如果你想要相应的值,你将不得不手动创建它。
你想要的是看起来像 {RED: "red", BLUE: "blue", CIRCLE: "circle", SQUARE: "square"}
的东西。有多种方法可以获取 Color
值和 Shape
值并制作这样一个对象。
你可以使用 the Object.assign()
method:
const ColorShape = Object.assign({}, Color, Shape);
// const ColorShape: typeof Color & typeof Shape
或者您可以使用 object spread:
const ColorShape = { ...Color, ...Shape };
/* const ColorShape: {
CIRCLE: Shape.CIRCLE;
SQUARE: Shape.SQUARE;
RED: Color.RED;
BLUE: Color.BLUE;
} */
任何一种方法都应该有效。完成此操作后,现在,您可以(大部分)使用 ColorShape
,就好像它是由 Color
和 Shape
:
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
s.
展示幕后发生的更多事情
我想合并两个枚举。
enum Color {
RED = 'red',
BLUE = 'blue',
}
enum Shape {
CIRCLE = 'circle',
SQUARE = 'square',
}
然而,当我声明联合类型并尝试引用成员时:
type ColorShape = Color | Shape;
const test: ColorShape = ColorShape.RED;
我遇到编译时错误:
TS2693: 'ColorShape' only refers to a type, but is being used as a value here.
这是为什么?枚举的联合不也是枚举本身吗?
重要的是要记住 TypeScript 类型 之间的 TypeScript 差异,这些差异存在于编译时并且 erased 来自发出的 JavaScript代码;和 JavaScript values,它们也存在于运行时并出现在发出的 JavaScript 代码中。 TypeScript 试图跟踪值和类型,但 JavaScript 只知道值。
大多数情况下,当您在 TypeScript 中进行声明时,您要么将值引入范围,要么将类型引入范围,但不会同时引入两者:
// values
const foo = 123;
function bar() { }
// types
type Baz = { a: number };
interface Qux { b: string }
上面的foo
和bar
是存在于发出的JavaScript中的值,而Baz
和Qux
是被擦除的类型.
并注意值和类型具有完全不同的命名空间;您可以拥有同名的值和类型,并且它们不必相互关联。编译器不会混淆它们:
// separate namespaces
const Xyz = { z: 1 };
type Xyz = { y: string };
这里Xyz
是一个值的名字(一个z
属性的对象,它的值为1
)也是一个类型的名字(一个y
属性 的对象类型,其值类型为 string
)。这些是无关的。如果我写 const uvw = Xyz;
那么我正在写 value-level 代码,使其成为 JavaScript,因此 Xyz
是值。如果我写 interface Rst extends Xyz { }
那么我正在写 type-level 从 JavaScript 中删除的代码,因此 Xyz
是类型。
好的,所以大多数时候当你声明一些命名的东西时,那个东西要么是一个类型,要么是一个值,但不是两者。但是有两个声明确实使同名的值和类型都存在:the class
declaration and the enum
declaration:
// both
class Abc { c = "" }
enum Def { D = "e" }
这里的名称 Abc
指的是一个 class 构造函数 value ,你可以像 new Abc()
一样在运行时引用它,它也引用一个 class 实例 type ,您可以在编译时使用它来谈论 class 实例,如 const instance: Abc = ...
。如果你写 const instance: Abc = new Abc()
你首先指的是类型,然后是值。
类似地,名称 Def
既指一个枚举对象 value,你可以像 Def.D === "e"
一样在运行时引用它,它也指一个enum type 是枚举成员值类型的联合,如 const def: Def = ...
。如果你写 const def: Def = Def.D
你首先指的是类型,然后是值。
现在,虽然 class
是 JavaScript 的一部分(自 ES2015 起),但 enum
不是。因此,当您编写 enum
时,编译器实际上会将其 downlevel 变为 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 ColorShape = ...
不是 enum
,所以您所做的只是创建类型。这里没有产生相应的值。您只完成了创建类似 enum
的组合的一半工作。如果你想要相应的值,你将不得不手动创建它。
你想要的是看起来像 {RED: "red", BLUE: "blue", CIRCLE: "circle", SQUARE: "square"}
的东西。有多种方法可以获取 Color
值和 Shape
值并制作这样一个对象。
你可以使用 the Object.assign()
method:
const ColorShape = Object.assign({}, Color, Shape);
// const ColorShape: typeof Color & typeof Shape
或者您可以使用 object spread:
const ColorShape = { ...Color, ...Shape };
/* const ColorShape: {
CIRCLE: Shape.CIRCLE;
SQUARE: Shape.SQUARE;
RED: Color.RED;
BLUE: Color.BLUE;
} */
任何一种方法都应该有效。完成此操作后,现在,您可以(大部分)使用 ColorShape
,就好像它是由 Color
和 Shape
:
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
s.