打字稿有联合,所以枚举是多余的吗?


77

自从TypeScript引入并集类型以来,我想知道是否有任何理由声明枚举类型。考虑以下枚举类型声明:

enum X { A, B, C }
var x:X = X.A;

和类似的联合类型声明:

type X: "A" | "B" | "C"
var x:X = "A";

如果它们基本上具有相同的目的,并且工会更强大且更具表现力,那么为什么枚举是必要的?


1
枚举映射到数字,我认为这在某些情况下会有所帮助。我假设他们最终还希望对枚举做更多的事情,例如给它们提供可以调用的属性(如c#或Java枚举。)
PaulBGD

Answers:


62

据我所知,它们不是多余的,这是由于非常简单的原因,即联合类型纯粹是一个编译时概念,而枚举实际上已经被转译并最终出现在生成的javascript中(样本)。

这使您可以对枚举执行某些操作,否则就无法使用联合类型(例如枚举可能的枚举值


1
我认为您的意思是说有理由使用enum。第一句话令人困惑。“据我所知,[枚举不是…。”回答问题“ [有任何理由声明枚举类型。”
马修

1
在我的阅读中,第一句“据我所知不是”是指标题问题“ ...那么枚举是否多余?”。。我正在提交相关程度的修改
manroe

40

使用最新版本的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在这些模式中起关键作用的断言。

为什么使用枚举不好?

1.非常量枚举不适合“ JavaScript的类型化超集”的概念

我认为这个概念是TypeScript在其他altJS语言中如此受欢迎的关键原因之一。非常量枚举通过使用与JavaScript不兼容的语法发出运行时中存在的JavaScript对象,从而违反了该概念。

2.常量枚举有一些陷阱

const枚举不能与Babel一起移植

当前有两个解决方法:手动或使用plugin摆脱const枚举babel-plugin-const-enum

在环境中声明const枚举可能会出现问题

--isolatedModules提供标志时,不允许使用环境常量。一个TypeScript团队成员说,const enum在DT上确实没有任何意义”(DT表示DefinitelyTyped),并且在环境上下文中,您应该使用文本的联合类型(字符串或数字)代替” const枚举。

--isolatedModules带有标志的const枚举即使在环境上下文之外也表现异常

我很惊讶在GitHub上阅读此评论,并确认该行为在TypeScript 3.8.2中仍然是正确的。

3.数字枚举类型不安全

您可以将任何数字分配给数字枚举。

enum ZeroOrOne {
  Zero = 0,
  One = 1
}
const zeroOrOne: ZeroOrOne = 2; // no error!!

4.声明字符串枚举可以是多余的

我们有时会看到这种字符串枚举:

enum Day {
  Sunday = 'Sunday',
  Monday = 'Monday',
  Tuesday = 'Tuesday',
  Wednesday = 'Wednesday',
  Thursday = 'Thursday',
  Friday = 'Friday',
  Saturday = 'Saturday'
}

5.联合类型模式凉爽得多

我在这个答案的开头显示的模式是TypeScript灵活和富有表现力的真实示例。我相信您可以更深入地了解TypeScript,并且通过积极采用这种模式可以成为一名优秀的TypeScript程序员。

我必须承认,联合类型无法实现枚举功能

即使从上下文可以明显看出字符串值包含在枚举中,也不能将其分配给枚举。

enum StringEnum {
  Foo = 'foo'
}
const foo1: StringEnum = StringEnum.Foo; // no error
const foo2: StringEnum = 'foo'; // error!!

通过消除使用字符串值或字符串文字,这在整个代码中统一了枚举值分配的样式。此行为与TypeScript类型系统在其他地方的行为不一致,这令人感到惊讶,有些人认为应该解决问题,并提出了问题(thisthis),其中反复提到了字符串枚举的意图是提供“不透明”的字符串类型:即可以在不修改使用者的情况下对其进行更改。

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-syntaxESLint的规则,如所述


嗯不错。但是,如果要将此声明放入共享的d.ts类型文件中怎么办?您只能在环境上下文
中将

1
我认为不可能迭代仅在环境上下文中声明的内容。同时提供声明declare const permissions: readonly ['read', 'write', 'execute']和真实的JavaScript对象const permissions = ['read', 'write', 'execute']
kimamula

1
我不希望字符串文字可以分配或与Enums相提并论。const foo: StringEnum === StringEnum.Foo // true or false 我希望此限制可确保我们没有字符串文字混合。
马修

@matthew谢谢您的评论。我将答案更新为公平。
kimamula

伟大的写作!但是,可能值得添加字符串联合类型的缺点,这与您对不透明性的观点有关:TypeScript不会区分在不同联合类型中使用的常量字符串(如“ read”)。因此,在查找此字符串的引用时,将显示使用该字符串的所有联合类型的引用。那就是为什么我也不能在这些字符串上执行“重命名符号”的主要原因。
雷内·汉堡

29

您可能没有理由使用 enum

我看到使用联合的最大优点是,它们提供了一种简洁的方式来表示多种类型的值,并且可读性强。 let x: number | string

编辑:从TypeScript 2.4开始,枚举现在支持字符串。

enum Colors {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE",
} 

4
我是唯一认为enum用于“位标志”的是反模式吗?
卡梅伦·塔克林德

14

从概念上讲,枚举可以看作是专用于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中的枚举替代项中,对其中的一些进行了很好的描述。


0

枚举类型不是多余的,但是在大多数情况下,union是首选。

但不总是。使用枚举表示例如状态转换可能比使用联合**更方便和更具表现力**

考虑真实的生活场景:

enum OperationStatus {
  NEW = 1,
  PROCESSING = 2,
  COMPLETED = 4
}

OperationStatus.PROCESSING > OperationStatus.NEW // true
OperationStatus.PROCESSING > OperationStatus.COMPLETED // false

2
我认为,如果执行此操作,则应在枚举声明中显式分配数字值,以防止重新排序(例如,因为其他人认为按字母顺序排列它们会更有意义)引入了可能被忽略的运行时错误。
dpoetzsch
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.