打字稿有工会,枚举是多余的?

prm*_*mph 38 enums unions typescript

自从TypeScript引入了联合类型以来,我想知道是否有任何理由声明枚举类型.请考虑以下枚举类型声明:

enum X { A, B, C }
var x:X = X.A;
Run Code Online (Sandbox Code Playgroud)

和类似的联合类型声明:

type X: "A" | "B" | "C"
var x:X = "A";
Run Code Online (Sandbox Code Playgroud)

如果它们基本上服务于同一目的,并且工会更强大和更具表现力,那么为什么需要枚举?

kim*_*ula 107

使用最新版本的 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
}
Run Code Online (Sandbox Code Playgroud)

当联合类型的实际值不能很好地描述它们自己时,您可以像使用枚举一样命名它们。

// 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
}
Run Code Online (Sandbox Code Playgroud)

不要错过as const在这些模式中起着关键作用的断言。

为什么使用枚举不好?

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

我认为这个概念是 TypeScript 在其他 altJS 语言中如此流行的关键原因之一。非常量枚举违反了这个概念,它发出的 JavaScript 对象存在于运行时,其语法与 JavaScript 不兼容。

2. 常量枚举有一些陷阱

常量枚举不能用 Babel 转译

目前有两种解决方法可以解决此问题:手动或使用 plugin 摆脱 const 枚举babel-plugin-const-enum

在环境上下文中声明 const 枚举可能有问题

--isolatedModules提供标志时,不允许使用环境常量枚举。一位 TypeScript 团队成员说const enum在 DT 上确实没有意义”(DT 指的是绝对类型)和“您应该使用联合类型的文字(字符串或数字)代替”环境上下文中的 const 枚举。

--isolatedModules即使在环境上下文之外,标记下的常量枚举也表现得很奇怪

我很惊讶地在 GitHub 上阅读了此评论,并确认该行为在 TypeScript 3.8.2 中仍然适用。

3. 数字枚举不是类型安全的

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

enum ZeroOrOne {
  Zero = 0,
  One = 1
}
const zeroOrOne: ZeroOrOne = 2; // no error!!
Run Code Online (Sandbox Code Playgroud)

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

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

enum Day {
  Sunday = 'Sunday',
  Monday = 'Monday',
  Tuesday = 'Tuesday',
  Wednesday = 'Wednesday',
  Thursday = 'Thursday',
  Friday = 'Friday',
  Saturday = 'Saturday'
}
Run Code Online (Sandbox Code Playgroud)

我不得不承认有一个枚举特性是联合类型没有实现的

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

enum StringEnum {
  Foo = 'foo'
}
const foo1: StringEnum = StringEnum.Foo; // no error
const foo2: StringEnum = 'foo'; // error!!
Run Code Online (Sandbox Code Playgroud)

这通过消除使用字符串值或字符串文字来统一整个代码中枚举值分配的风格。这种行为与 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;
Run Code Online (Sandbox Code Playgroud)

请注意,这种“不透明性”并不完美,因为将枚举值分配给字符串文字类型不受限制。

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;
Run Code Online (Sandbox Code Playgroud)

如果你认为这个“不透明”特性是如此有价值,以至于你可以接受我上面描述的所有缺点来换取它,你就不能放弃字符串枚举。

如何从代码库中消除枚举

随着no-restricted-syntaxESLint的规则,如描述

  • 我不希望字符串文字可分配或与枚举进行比较。`const foo: StringEnum === StringEnum.Foo // true or false` 我想要这个限制来确保我们没有字符串文字混淆。 (4认同)
  • 使用枚举,可以更轻松地找到项目中某个值的所有用法,例如重命名、添加、删除新值……对我来说,当我忘记 switch 中的情况时,我的 IDE 会发出警告,这非常方便陈述。 (2认同)

Ami*_*mid 29

据我所知它们不是,由于非常简单的原因,联合类型纯粹是一个编译时的概念,而枚举实际上是在生成的javascript(样本)中进行转换.

这允许你用枚举做一些事情,否则联合类型是不可能的(比如枚举可能的枚举值)

  • 在我的阅读中,第一句话的“据我所知他们不是”指的是标题问题中的枚举“......所以枚举是多余的吗?” . 我正在提交这样的编辑 (3认同)
  • 我认为您的意思是说有理由使用“枚举”。第一句话令人困惑。“据我所知,[枚举] 不是……”回答问题时“[有] 任何理由声明枚举类型。” (2认同)

mat*_*hew 19

您可能没有理由使用 enum

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

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

enum Colors {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE",
} 
Run Code Online (Sandbox Code Playgroud)

  • 我是唯一一个认为使用“enum”作为“位标志”是一种反模式的人吗? (6认同)
  • 取决于你在做什么。Bit-twiddling 并不像以前在 JavaScript 中那么罕见。 (2认同)

Rom*_*eau 18

枚举可以在概念上被视为联合类型的子集,专用于int和/或string值,在其他响应中提到了一些额外的功能,使它们易于使用,例如namespace

关于类型安全,数字枚举不太安全,然后是联合类型,最后是字符串枚举:

// 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'.
Run Code Online (Sandbox Code Playgroud)

这篇 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();
}
Run Code Online (Sandbox Code Playgroud)

在枚举和联合类型之间,单例可以代替枚举。它更冗长,但也更面向对象

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);
Run Code Online (Sandbox Code Playgroud)

我倾向于查看 F# 以了解良好的建模实践。引用F# enums on F#一篇文章以获得乐趣和利润,在这里很有用:

一般来说,你应该更喜欢有区别的联合类型而不是枚举,除非你真的需要有一个int (或一个string与它们关联的值

模型枚举还有其他替代方法。其中一些在其他 2ality 文章中对 TypeScript 中的枚举的替代方法进行了很好的描述。