在TypeScript中,枚举和对象有什么区别?

83

我正在尝试通过枚举访问映射中的值,并创建一个可翻译的应用程序来处理其中的所有字符串。这两个问题有所重叠,我必须决定是使用枚举还是JSON格式中的对象。

那么,枚举和对象之间的区别和用法究竟是什么呢?

例如:

  • 我可以使用枚举访问数组,插入标签和其他内容

const enum FieldNames {
  FirstField: "Field One",
  SecondField: "Field Two"
};
    
someFieldArray[FieldNames.FirstField].label = FieldNames.FirstField;
someFieldArray[FieldNames.SecondField].label = FieldNames.SecondField;

  • 或者我可以通过对象实现相同的行为

const FieldNames = {
  FirstField: "Field One",
  SecondField: "Field Two"
};

someFieldArray[FieldNames.FirstField].label = FieldNames.FirstField;
someFieldArray[FieldNames.SecondField].label = FieldNames.SecondField;

我真的不明白为什么要选择枚举而不是简单对象。在我看来,一个对象有更多的好处而没有任何缺点。

5个回答

108

枚举(Enum)

如果你需要以下特性,枚举可能会给你带来额外的好处:

const enum FieldNamesEnum {
  FirstField = "Field One",
  SecondField = "Field Two"
};

let x: FieldNamesEnum;

x = FieldNamesEnum.FirstField;
x = FieldNamesEnum.SecondField;

// Error - not assignable to FieldNames
x = 'str';

// Cannot assign
FieldNamesEnum.FirstField = 'str';

重要的是,您无法将值分配给枚举成员,并且类型检查是基于枚举成员而不是字符串。

此外,因为您在示例中使用了const enum,所以该枚举在运行时不存在,所有引用都将替换为文字值(如果您使用了普通enum,则该枚举将在运行时存在)。

对象

与对象示例进行比较:

const FieldNames = {
  FirstField: "Field One",
  SecondField: "Field Two"
};

let y: string;

y = FieldNames.FirstField;
y = FieldNames.SecondField;

// Oops it works
y = 'str';

// Oops it works

FieldNames.FirstField = 'str';

Union

如果您不需要完整的枚举,但想限制值,则可以使用字面值联合:

type FieldNames = "Field One" | "Field Two";

let x: FieldNames;

x = "Field One";
x = "Field Two";

// Error - not allowed
x = "Field Three";

但是是否可能创建封装的枚举? - Florian Leitgeb
2
太棒了的解释!!特别是与枚举成员未分配相关的那个。 - shanti
1
你的解决方案存在问题,因为你将y的类型设置为string,而不是从@Martynas Skučas下面提供的FieldNames对象的实际值类型。这里是链接:https://dev59.com/e1YN5IYBdhLWcg3wfoNj#59932722 - Florian Leitgeb
1
枚举的行为可以使用对象来实现。 - howard wolowitz

31

我不同意@Fenton的观点。对象是类型安全的。

const FieldNames = {
  FirstField: 'Field One',
  SecondField: 'Field Two',
} as const;

type ValueOf<T> = T[keyof T];
let y: ValueOf<typeof FieldNames>;

y = FieldNames.FirstField;
y = FieldNames.SecondField;

// TS2322: Type '"str"' is not assignable to type '"Field One" | "Field Two"'.
y = 'str';

// TS2540: Cannot assign to 'FirstField' because it is a read-only property
FieldNames.FirstField = 'str';

5
我喜欢你的想法。将“y”设置为字符串类型不是最好的方法。在我看来,这是非常好的回答。然而,它并没有回答整个问题。 - Florian Leitgeb
6
“as const” 是在 2019 年发布的 TypeScript 3.4 中引入的。而 @Fenton 的原始答案是在 2017 年发布的。我认为这是一个有效的更新答案。事实上,TypeScript 官方文档现在表示,大多数情况下,“as const”可以用来替换枚举(enum)。 - coolgod
1
@coolgod,你能告诉我在文档中哪里提到了“as const将大多数情况下替代枚举”的内容吗?我找不到。 - Edu Ruiz
3
@EduRuiz https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums - Robula

11
function (foo: FieldNames) { }
如果FieldNames是一个对象,这意味着该函数期望一个具有属性FirstFieldSecondField的实例。如果FieldNames是一个枚举(此时应为单数形式而不是复数形式),则该函数期望FieldNames的值之一,即"Field One""Field Two"
非常不同的用法。

但是你可以通过仅使用接口和/或类来实现这种行为。 - Florian Leitgeb
你如何轻松地定义一个接口或类,以表达“其中一个值”? - deceze
一个带有静态FieldName属性的抽象类?或者类似于这样的东西。 - Florian Leitgeb
以上类型提示表示 foo 可以是值为 "Field One""Field Two"。它可以写成 foo: "Field One" | "Field Two"。如何将其表达为具有静态属性的类? - deceze
2
枚举作为类型提示表示“变量可以取此列表中的一个值”。将枚举作为对象,可以通过类似常量(例如FieldName.FirstField)的方式轻松访问这些值;您可以更改底层值,而无需重写使用它的所有代码。- 这与类/对象非常不同,您可以从类实例化对象并传递该对象,并根据对象类型进行类型提示。 - deceze
2
你可以使用 function (foo: keyof FieldNames) { } 来代替。 - Daniel Kucal

5
在Typescript语言手册中,有一节专门讨论此事:对象(Objects)与枚举(Enums)
个人而言,当我需要一个包含一组选项的类型时,我更喜欢使用Typescript枚举,因为它更容易使用。通过枚举定义,您既有了类型,也有了访问选项的方式。要使用对象实现相同的方法,您需要将对象定义为as const,并基于它定义一个额外的类型。

1

这应该是主要原因...你可以使用枚举作为一种类型,所以以下其中一种选项,但你不能将常量对象定义为一种类型,它将在一个对象中具有所有选项 :)

你也可以这样做

type FieldNames = "Field One" | "Field Two";

但是如果列表很长,枚举会更好, 在ES6中,枚举纯粹没有用处,只需使用对象 但在TypeScript中,它是一种类型,并且在我看来是一个主要的优势

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接