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

时间:2016-10-27 03:53:12

标签: typescript enums unions

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

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

和类似的联合类型声明:

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

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

5 个答案:

答案 0 :(得分:45)

使用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。非const枚举不适合“ JavaScript的类型化超集”的概念

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

2。 const枚举有一些陷阱

Const枚举不能与Babel一起移植

当前有two workarounds个问题可解决:手动或通过插件babel-plugin-const-enum摆脱const枚举。

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

提供--isolatedModules标志时,不允许使用环境常量。 一个TypeScript小组成员说,"const enum on DT really does not make sense"(DT表示DefinitelyTyped)和"You should use a union type of literals (string or number) instead"是环境上下文中的常量枚举。

--isolatedModules标志下的const枚举即使在环境上下文之外的行为也很奇怪

我很惊讶地在GitHub上阅读this comment,并确认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'
}

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

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

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;

如果您认为此“不透明”功能非常有价值,以至于可以接受我上面描述的所有缺点来换取它,则您不能放弃字符串枚举。

如何从代码库中消除枚举

使用ESLint的no-restricted-syntax规则,即described

答案 1 :(得分:18)

据我所知,它们并不是多余的,因为联合类型纯粹是编译时概念的简单原因,而枚举实际上是在生成的javascript(sample)中进行转换。

这允许你用枚举做一些事情,否则联合类型就不可能了(比如enumerating the possible enum values

答案 2 :(得分:14)

您可能希望使用enum

的原因很少

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

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

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

答案 3 :(得分:1)

从概念上讲,枚举可以看作是联合类型的子集,专用于int和/或string值,其他响应中提到了一些附加功能,使它们易于使用。

但是枚举并不完全安全:

enum Colors { Red, Green, Blue }
const c: Colors = 100; // No errors!

type Color =
    | 0 | 'Red'
    | 1 | 'Green'
    | 2 | 'Blue';

const c2: Color = 100; // Error: Type '100' is not assignable to type 'Color'

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

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: ReadonlyArray<Color> = [
        Color.Red,
        Color.Green,
        Color.Blue,
    ]; // `[...] as readonly` to infer `ReadonlyArray<Color>` in TypeScript 3.4

    private constructor(
        readonly id: number,
        readonly label: string,
        readonly hex: string) { }
}

const c = Color.Red;

const colorIds = Color.All.map(x => x.id);

我倾向于看F#以了解良好的建模实践。来自article on F# enums on F# for fun and profit的引文,可能在此处有用:

  

通常,除非确实需要与它们关联的int (或string值,否则您应优先使用区分的联合类型而不是枚举。

答案 4 :(得分:0)

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

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

考虑真实的生活场景:

enum OperationStatus {
  NEW,
  PROCESSING,
  COMPLETED
}

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