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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/40275832/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 03:58:03  来源:igfitidea点击:

Typescript has unions, so are enums redundant?

typescriptenumsunions

提问by prmph

Ever since TypeScript introduced unions types, I wonder if there is any reason to declare an enum type. Consider the following enum type declaration:

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

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

and a similar union type declaration:

和类似的联合类型声明:

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

If they basically serve the same purpose, and unions are more powerful and expressive, then why are enums necessary?

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

采纳答案by Amid

As far as I see they are not redundant, due to the very simple reason that union types are purely a compile time concept whereas enums are actually transpiled and end up in the resulting javascript (sample).

据我所知,它们并不是多余的,原因很简单,联合类型纯粹是一个编译时概念,而枚举实际上被转换并最终出现在结果 javascript ( sample) 中。

This allows you to do some things with enums, that are otherwise impossible with union types (like enumerating the possible enum values)

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

回答by matthew

There are few reasons you might want to use an enum

您可能想要使用 enum

I see the big advantages of using a union is that they provide a succinct way to represent a value with multiple types and they are very readable. let x: number | string

我看到使用联合的最大优势在于它们提供了一种简洁的方式来表示具有多种类型的值,并且它们非常易读。 let x: number | string

EDIT: As of TypeScript 2.4 Enums now support strings.

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

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

回答by Romain Deneau

Enums can be seen conceptually as a subset of union types, dedicated to intand/or stringvalues, with a few additional features mentioned in other responses that make them friendly to use, e.g. namespace.

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

Regarding type safety, numeric enums are the less safe, then come union types and finally string enums:

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

// 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'.

More on that topic in this 2ality article: TypeScript enums: How do they work? What can they be used for?

这篇 2ality 文章中有关该主题的更多信息:TypeScript 枚举:它们是如何工作的?它们可以用来做什么?



Union types support heterogenous data and structures, enabling polymorphism for instance:

联合类型支持异构数据和结构,例如支持多态:

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();
}


In between enums and union types, singletons can replace enums. It's more verbose but also more object-oriented:

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

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);

I tend to look at F# to see good modeling practices. A quote from an article on F# enums on F# for fun and profitthat can be useful here:

我倾向于查看 F# 以了解良好的建模实践。引用F# 上关于 F# 枚举文章的引用,以获取乐趣和利润,此处可能有用:

In general, you should prefer discriminated union types over enums, unless you really need to have an int(or a string)value associated with them

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

There are other alternatives to model enums. Some of them are well described in this other 2ality article Alternatives to enums in TypeScript.

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

回答by kimamula

With the recent versions of TypeScript, it is easy to declare iterable union types. Therefore, you should prefer union types to enums.

使用最新版本的 TypeScript,很容易声明可迭代的联合类型。因此,您应该更喜欢联合类型而不是枚举。

How to declare iterable union types

如何声明可迭代联合类型

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 the actual values of the union type do not describe theirselves very well, you can name them as you do with enums.

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

// 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
}

Do not miss as constassertion which plays the crucial role in these patterns.

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

Why it is not good to use enums?

为什么使用枚举不好?

1. Non-const enums do not fit to the concept "a typed superset of JavaScript"

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

I think this concept is one of the crucial reasons why TypeScript has become so popular among other altJS languages. Non-const enums violate the concept by emitting JavaScript objects that live in runtime with a syntax that is not compatible with JavaScript.

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

2. Const enums have some pitfalls

2. 常量枚举有一些陷阱

Const enums cannot be transpiled with Babel

常量枚举不能用 Babel 转译

There are currently two workaroundsfor this issue: to get rid of const enums manually or with plugin babel-plugin-const-enum.

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

Declaring const enums in an ambient context can be problematic

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

Ambient const enums are not allowed when the --isolatedModulesflag is provided. A TypeScript team member says that "const enumon DT really does not make sense"(DT refers to DefinitelyTyped) and "You should use a union type of literals (string or number) instead"of const enums in ambient context.

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

Const enums under --isolatedModulesflag behave strangely even outside an ambient context

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

I was surprised to read this commenton GitHub and confirmed that the behavior is still true with TypeScript 3.8.2.

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

3. Numeric enums are not type safe

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

You can assign any number to numeric enums.

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

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

4. Declaration of string enums can be redundant

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

We sometimes see this kind of string enums:

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

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

5. The union types pattern is much cooler

5. union types 模式更酷

The patterns I have shown in the beginning of this answer are the great real-world examples of how flexible and expressive TypeScript is. I believe you can understand TypeScript more deeply and can be a good TypeScript programmer by adopting such patterns aggressively.

我在本答案开头展示的模式是真实世界的例子,展示了 TypeScript 的灵活性和表现力。我相信你可以更深入地理解 TypeScript,积极采用这样的模式,也可以成为一名优秀的 TypeScript 程序员。

I have to admit that there is an enum feature that does not achieved by union types

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

Even if it is obvious from the context that the string value is included in the enum, you cannot assign it to the enum.

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

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

This unifies the style of enum value assignment throughout the code by eliminating the use of string values or string literals. This behavior is not consistent with how TypeScript type system behaves in the other places and is kind of surprising and some people who thought this should be fixed raised issues (thisand this), in which it is repeatedly mentioned that the intent of string enums is to provide "opaque" string types: i.e. they can be changed without modifying consumers.

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

Note that this "opaqueness" is not perfect as the assignment of enum values to string literal types is not limited.

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

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;

If you think this "opaque" feature is so valuable that you can accept all the drawbacks I described above in exchange for it, you cannot abandon string enums.

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

How to eliminate enums from your codebase

如何从代码库中消除枚举

With the no-restricted-syntaxrule of ESLint, as described.

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

回答by John Smith

The enum type is not redundant, but in most cases union is preferred.

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

But not always. Using enums to represents e.g state transitions could be much more handy and expressive than using union**

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

Consider real live scenario:

考虑真实的现场场景:

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

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