自从TypeScript引入并集类型以来,我想知道是否有任何理由声明枚举类型。考虑以下枚举类型声明:
enum X { A, B, C }
var x:X = X.A;
和类似的联合类型声明:
type X: "A" | "B" | "C"
var x:X = "A";
如果它们基本上具有相同的目的,并且工会更强大且更具表现力,那么为什么枚举是必要的?
Answers:
使用最新版本的TypeScript,可以轻松声明可迭代的联合类型。因此,与枚举相比,您应该更喜欢联合类型。
const permissions = ['read', 'write', 'execute'] as const;
type Permission = typeof permissions[number]; // 'read' | 'write' | 'execute'
// you can iterate over permissions
for (const permission of permissions) {
// do something
}
当并集类型的实际值不能很好地描述其自身时,可以像对枚举一样命名它们。
// when you use enum
enum Permission {
Read = 'r',
Write = 'w',
Execute = 'x'
}
// union type equivalent
const Permission = {
Read: 'r',
Write: 'w',
Execute: 'x'
} as const;
type Permission = typeof Permission[keyof typeof Permission]; // 'r' | 'w' | 'x'
// of course it's quite easy to iterate over
for (const permission of Object.values(Permission)) {
// do something
}
不要错过as const
在这些模式中起关键作用的断言。
我认为这个概念是TypeScript在其他altJS语言中如此受欢迎的关键原因之一。非常量枚举通过使用与JavaScript不兼容的语法发出运行时中存在的JavaScript对象,从而违反了该概念。
当前有两个解决方法:手动或使用plugin摆脱const枚举babel-plugin-const-enum
。
--isolatedModules
提供标志时,不允许使用环境常量。一个TypeScript团队成员说,“const enum
在DT上确实没有任何意义”(DT表示DefinitelyTyped),并且“在环境上下文中,您应该使用文本的联合类型(字符串或数字)代替” const枚举。
--isolatedModules
带有标志的const枚举即使在环境上下文之外也表现异常我很惊讶在GitHub上阅读此评论,并确认该行为在TypeScript 3.8.2中仍然是正确的。
您可以将任何数字分配给数字枚举。
enum ZeroOrOne {
Zero = 0,
One = 1
}
const zeroOrOne: ZeroOrOne = 2; // no error!!
我们有时会看到这种字符串枚举:
enum Day {
Sunday = 'Sunday',
Monday = 'Monday',
Tuesday = 'Tuesday',
Wednesday = 'Wednesday',
Thursday = 'Thursday',
Friday = 'Friday',
Saturday = 'Saturday'
}
我在这个答案的开头显示的模式是TypeScript灵活和富有表现力的真实示例。我相信您可以更深入地了解TypeScript,并且通过积极采用这种模式可以成为一名优秀的TypeScript程序员。
即使从上下文可以明显看出字符串值包含在枚举中,也不能将其分配给枚举。
enum StringEnum {
Foo = 'foo'
}
const foo1: StringEnum = StringEnum.Foo; // no error
const foo2: StringEnum = 'foo'; // error!!
通过消除使用字符串值或字符串文字,这在整个代码中统一了枚举值分配的样式。此行为与TypeScript类型系统在其他地方的行为不一致,这令人感到惊讶,有些人认为应该解决此问题,并提出了问题(this和this),其中反复提到了字符串枚举的意图是提供“不透明”的字符串类型:即可以在不修改使用者的情况下对其进行更改。
enum Weekend {
Saturday = 'Saturday',
Sunday = 'Sunday'
}
// As this style is forced, you can change the value of
// Weekend.Saturday to 'Sat' without modifying consumers
const weekend: Weekend = Weekend.Saturday;
请注意,这种“不透明”并不完美,因为枚举值到字符串文字类型的分配不受限制。
enum Weekend {
Saturday = 'Saturday',
Sunday = 'Sunday'
}
// The change of the value of Weekend.Saturday to 'Sat'
// results in a compilation error
const saturday: 'Saturday' = Weekend.Saturday;
如果您认为此“不透明”功能非常有价值,以至于可以接受我上面描述的所有缺点来换取它,则您不能放弃字符串枚举。
使用no-restricted-syntax
ESLint的规则,如所述。
declare const permissions: readonly ['read', 'write', 'execute']
和真实的JavaScript对象const permissions = ['read', 'write', 'execute']
。
const foo: StringEnum === StringEnum.Foo // true or false
我希望此限制可确保我们没有字符串文字混合。
您可能没有理由使用 enum
enum
。 enum
as flags
。位标志 我看到使用联合的最大优点是,它们提供了一种简洁的方式来表示多种类型的值,并且可读性强。
let x: number | string
编辑:从TypeScript 2.4开始,枚举现在支持字符串。
enum Colors {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
enum
用于“位标志”的是反模式吗?
从概念上讲,枚举可以看作是专用于int
和/或string
值的联合类型的子集,其他响应中提到了一些使其易于使用的其他功能,例如命名空间。
关于类型安全性,数字枚举的安全性较低,然后是联合类型,最后是字符串枚举:
// Numeric enum
enum Colors { Red, Green, Blue }
const c: Colors = 100; // ⚠️ No errors!
// Equivalent union types
type Color =
| 0 | 'Red'
| 1 | 'Green'
| 2 | 'Blue';
let color: Color = 'Red'; // ✔️ No error because namespace free
color = 100; // ✔️ Error: Type '100' is not assignable to type 'Color'
type AltColor = 'Red' | 'Yellow' | 'Blue';
let altColor: AltColor = 'Red';
color = altColor; // ⚠️ No error because `altColor` type is here narrowed to `"Red"`
// String enum
enum NamedColors {
Red = 'Red',
Green = 'Green',
Blue = 'Blue',
}
let namedColor: NamedColors = 'Red'; // ✔️ Error: Type '"Red"' is not assignable to type 'Colors'.
enum AltNamedColors {
Red = 'Red',
Yellow = 'Yellow',
Blue = 'Blue',
}
namedColor = AltNamedColors.Red; // ✔️ Error: Type 'AltNamedColors.Red' is not assignable to type 'Colors'.
在2ality文章中,有关该主题的更多信息:TypeScript枚举:它们如何工作?它们可以用来做什么?
联合类型支持异构数据和结构,例如支持多态:
class RGB {
constructor(
readonly r: number,
readonly g: number,
readonly b: number) { }
toHSL() {
return new HSL(0, 0, 0); // Fake formula
}
}
class HSL {
constructor(
readonly h: number,
readonly s: number,
readonly l: number) { }
lighten() {
return new HSL(this.h, this.s, this.l + 10);
}
}
function lightenColor(c: RGB | HSL) {
return (c instanceof RGB ? c.toHSL() : c).lighten();
}
在枚举和联合类型之间,单例可以替换枚举。它比较冗长,但也更面向对象:
class Color {
static readonly Red = new Color(1, 'Red', '#FF0000');
static readonly Green = new Color(2, 'Green', '#00FF00');
static readonly Blue = new Color(3, 'Blue', '#0000FF');
static readonly All: readonly Color[] = [
Color.Red,
Color.Green,
Color.Blue,
];
private constructor(
readonly id: number,
readonly label: string,
readonly hex: string) { }
}
const c = Color.Red;
const colorIds = Color.All.map(x => x.id);
我倾向于看F#以了解良好的建模实践。关于F#的文章的引言列举了F#的乐趣和收益,在这里可能有用:
通常,除非确实需要与它们关联的
int
(或string
)值,否则您应优先选择区分的联合类型。
还有其他替代方法来枚举枚举。在另2ality文章TypeScript中的枚举替代项中,对其中的一些进行了很好的描述。
枚举类型不是多余的,但是在大多数情况下,union是首选。
但不总是。使用枚举表示例如状态转换可能比使用联合**更方便和更具表现力**
考虑真实的生活场景:
enum OperationStatus {
NEW = 1,
PROCESSING = 2,
COMPLETED = 4
}
OperationStatus.PROCESSING > OperationStatus.NEW // true
OperationStatus.PROCESSING > OperationStatus.COMPLETED // false