如何以编程方式枚举枚举类型?

99

假设我有一个TypeScript的enum,名为MyEnum,代码如下:

enum MyEnum {
    First,
    Second,
    Third
}

在TypeScript 0.9.5中,生成枚举值数组的最佳方式是什么?例如:

var choices: MyEnum[]; // or Array<MyEnum>
choices = MyEnum.GetValues(); // plans for this?
choices = EnumEx.GetValues(MyEnum); // or, how to roll my own?
13个回答

198

这是该枚举的JavaScript输出:

var MyEnum;
(function (MyEnum) {
    MyEnum[MyEnum["First"] = 0] = "First";
    MyEnum[MyEnum["Second"] = 1] = "Second";
    MyEnum[MyEnum["Third"] = 2] = "Third";
})(MyEnum || (MyEnum = {}));

就像这样的一个物体:

{
    "0": "First",
    "1": "Second",
    "2": "Third",
    "First": 0,
    "Second": 1,
    "Third": 2
}

具有字符串值的枚举成员

TypeScript 2.4 增加了枚举可能具有字符串枚举成员值的功能。因此,可能会得到一个类似以下的枚举:

enum MyEnum {
    First = "First",
    Second = 2,
    Other = "Second"
}

// compiles to
var MyEnum;
(function (MyEnum) {
    MyEnum["First"] = "First";
    MyEnum[MyEnum["Second"] = 2] = "Second";
    MyEnum["Other"] = "Second";
})(MyEnum || (MyEnum = {}));

获取成员名称

我们可以查看上面的示例,尝试弄清如何获取枚举成员:

{
    "2": "Second",
    "First": "First",
    "Second": 2,
    "Other": "Second"
}

这是我想到的:

const e = MyEnum as any;
const names = Object.keys(e).filter(k => 
    typeof e[k] === "number"
    || e[k] === k
    || e[e[k]]?.toString() !== k
);

成员变量

一旦我们拥有了这些名称,我们就可以通过循环来获取相应的值,方法如下:

const values = names.map(k => MyEnum[k]);

扩展类

我认为最好的方法是创建你自己的函数(例如 EnumEx.getNames(MyEnum))。你不能向一个枚举类型中添加函数。

class EnumEx {
    private constructor() {
    }

    static getNamesAndValues(e: any) {
        return EnumEx.getNames(e).map(n => ({ name: n, value: e[n] as string | number }));
    }

    static getNames(e: any) {
        return Object.keys(e).filter(k => 
            typeof e[k] === "number"
            || e[k] === k
            || e[e[k]]?.toString() !== k
        );
    }

    static getValues(e: any) {
        return EnumEx.getNames(e).map(n => e[n] as string | number);
    }
}

有趣的是(因为许多人已经点赞了这个答案),我无法在TS Playground中使其工作:https://shorturl.me/jJ8G2t。我做错了什么吗? - Peter
1
@Peter 我更新了答案,包括有关字符串枚举的信息。另外,你应该使用 for of 语句而不是 for in - David Sherret

31

使用TypeScript >= 2.4,您可以定义字符串枚举:

enum Color {
  RED = 'Red',
  ORANGE = 'Orange',
  YELLOW = 'Yellow',
  GREEN = 'Green',
  BLUE = 'Blue',
  INDIGO = 'Indigo',
  VIOLET = 'Violet'
}

JavaScript ES5 输出:

var Color;
(function (Color) {
    Color["RED"] = "Red";
    Color["ORANGE"] = "Orange";
    Color["YELLOW"] = "Yellow";
    Color["GREEN"] = "Green";
    Color["BLUE"] = "Blue";
    Color["INDIGO"] = "Indigo";
    Color["VIOLET"] = "Violet";
})(Color || (Color = {}));

这是一个像这样的对象:

const Color = {
  "RED": "Red",
  "ORANGE": "Orange",
  "YELLOW": "Yellow",
  "GREEN": "Green",
  "BLUE": "Blue",
  "INDIGO": "Indigo",
  "VIOLET": "Violet"
}

因此,在字符串枚举的情况下,无需过滤任何内容,Object.keys(Color)Object.values(Color) 就足够了:

const colorKeys = Object.keys(Color) as (keyof typeof Color)[];
console.log('colorKeys =', colorKeys);
// ["RED", "ORANGE", "YELLOW", "GREEN", "BLUE", "INDIGO", "VIOLET"]

const colorValues = Object.values(Color);
console.log('colorValues =', colorValues);
// ["Red", "Orange", "Yellow", "Green", "Blue", "Indigo", "Violet"]

colorKeys.map(colorKey => {
  console.log(`color key = ${colorKey}, value = ${Color[colorKey]}`);
});
/*
color key = RED, value = Red
color key = ORANGE, value = Orange
color key = YELLOW, value = Yellow
color key = GREEN, value = Green
color key = BLUE, value = Blue
color key = INDIGO, value = Indigo
color key = VIOLET, value = Violet
*/

请参见 TypeScript Playground 上的示例


这里出现了错误:元素隐式具有“any”类型,因为类型为“string”的表达式无法用于索引类型为“typeof Color”的类型。在'typeof Color'上找不到具有类型'string'的参数的索引签名。 - Jonas
1
@Jonas 我已经用类型转换解决了:Object.keys(Color) as (keyof typeof Color)[] - tanguy_k

10

在TypeScript(即反射)中,没有RTTI(运行时类型信息)的概念,因此要实现这一点,需要了解转译后的JavaScript知识。因此,假设使用TypeScript 0.95:

enum MyEnum {
    First, Second, Third
}

变成:

var MyEnum;
(function(MyEnum) {
    MyEnum[MyEnum["First"] = 0] = "First";
    MyEnum[MyEnum["Second"] = 1] = "Second";
    MyEnum[MyEnum["Third"] = 2] = "Third";
}

因此,在JavaScript中,它被建模为常规对象,其中MyEnum.0 == "First"MyEnum.First == 0。因此,要枚举所有枚举名称,您需要获取属于对象并且也不是数字的所有属性:

for (var prop in MyEnum) {         
    if (MyEnum.hasOwnProperty(prop) &&
        (isNaN(parseInt(prop)))) {
        console.log("name: " + prop);
    }
}

好的,现在我告诉了你如何做到这一点,我可以告诉你这是一个不好的想法。你不是在写一个受控语言,所以你不能带上这些习惯。它仍然只是普通的JavaScript。如果我想在JavaScript中使用一个结构来填充某种选择列表,我会使用一个普通的数组。枚举在这里不是正确的选择,双关意味着。TypeScript的目标是生成惯用的、漂亮的JavaScript。在这种方式下使用枚举并不能保留这个目标。


9
您可以添加函数以获取枚举的名称和索引:
enum MyEnum {
  First,
  Second,
  Third
}

namespace MyEnum {
  function isIndex(key):boolean {
    const n = ~~Number(key);
    return String(n) === key && n >= 0;
  }

  const _names:string[] = Object
      .keys(MyEnum)
      .filter(key => !isIndex(key));

  const _indices:number[] = Object
      .keys(MyEnum)
      .filter(key => isIndex(key))
      .map(index => Number(index));

  export function names():string[] {
    return _names;
  }

  export function indices():number[] {
    return _indices;
  }
}

console.log("MyEnum names:", MyEnum.names());
// Prints: MyEnum names: ["First", "Second", "Third"]

console.log("MyEnum indices:", MyEnum.indices());
// Prints: MyEnum indices: [0, 1, 2]

请注意,您可以仅导出_names_indices常量,而不是通过导出函数来公开它们。但是,因为导出的成员是枚举的成员,所以将它们作为函数更清晰,这样它们就不会与实际的枚举成员混淆。
如果TypeScript自动为所有枚举生成类似的内容,那将是很好的。

5
我使用了David Sherret提出的解决方案,并编写了一个npm库,您可以使用它命名为enum-values... Git: enum-values
// Suppose we have an enum
enum SomeEnum {
  VALUE1,
  VALUE2,
  VALUE3
}

// names will be equal to: ['VALUE1', 'VALUE2', 'VALUE3']
var names = EnumValues.getNames(SomeEnum);

// values will be equal to: [0, 1, 2]
var values = EnumValues.getValues(SomeEnum);

3

一条命令可以获得一个条目列表(键值对象/对):

Object.keys(MyEnum).filter(a=>a.match(/^\D/)).map(name=>({name, value: MyEnum[name] as number}));

2

如果您想将字符串值与枚举关联起来,这些方法都不适用。要拥有通用功能,您可以执行以下操作:

function listEnum(enumClass) {
    var values = [];
    for (var key in enumClass) {
        values.push(enum[key]);
    }
    values.length = values.length / 2;
    return values;
}

这段代码的作用是,TypeScript会先添加键(keys),再添加值(values)。

在TypeScript中,它是这样写的:

var listEnums = <T> (enumClass: any): T[]=> {
    var values: T[] = [];
    for (var key in enumClass) {
        values.push(enumClass[key]);
    }
    values.length = values.length / 2;
    return values;
};

var myEnum: TYPE[] = listEnums<TYPE>(TYPE);

2
enum MyEnum {
    First, Second, Third, NUM_OF_ENUMS
}

for(int i = 0; i < MyEnum.NUM_OF_ENUMS; ++i) {
    // do whatever you need to do.
}

5
只有在枚举类型没有定义任何值(即紧密排列且递增)时,此方法才有效。例如,枚举值可能是“First = 0x1000”或“PageNotFound = 404”。NUM_OF_ENUMS将始终比最大定义的值大1,因此在我的示例中为0x1001或405。 - Aku

2

枚举迭代

最好使用字符串枚举来进行迭代。以下是一个示例:

// This is a string enum
enum MyEnum {
    First = 'First',
    Second = 'Second',
    Third = 'Third',
}

// An enum is a TS concept
// However his MyEnum compiles to JS object:
//  {
//   "First": "First",
//   "Second": "Second",
//   "Third": "Third"
// } 


// Therefore we can get the keys in the following manner:
const keysArray = Object.keys(MyEnum);

for (const key of keysArray) {
    console.log(key)
}
// [LOG]: "First" 
// [LOG]: "Second" 
// [LOG]: "Third" 

1

joe's answer 让我意识到,依靠前N个数字键比进行更复杂的测试更容易:

function getEnumMembers(myEnum): string[]
{
    let members = []
    for(let i:number = 0; true; i++) {
        if(myEnum[i] === undefined) break
        members.push(myEnum[i])
    }

    return members
}

enum Colors {
    Red, Green, Blue
}

console.log(getEnumMembers(myEnum))

4
这是一个危险的假设,因为可以定义分配给枚举的值,它们不需要是递增和紧密打包的。例如,在枚举中看到位掩码或者以400开始的HTML错误代码表并不罕见。 - Aku
https://dev59.com/jW035IYBdhLWcg3wQtsg - Ruan Mendes

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