¿Cómo enumerar mediante programación un tipo de enumeración?


90

Digamos que tengo un TypeScript enum, de la MyEnumsiguiente manera:

enum MyEnum {
    First,
    Second,
    Third
}

¿Cuál sería la mejor manera en TypeScript 0.9.5 de producir una matriz de enumvalores? Ejemplo:

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

Respuestas:


192

Esta es la salida de JavaScript de esa enumeración:

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

Que es un objeto como este:

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

Miembros de enumeración con valores de cadena

TypeScript 2.4 agregó la capacidad para que las enumeraciones posiblemente tengan valores de miembro de enumeración de cadena. Entonces, es posible terminar con una enumeración similar a la siguiente:

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 = {}));

Obtener nombres de miembros

Podemos ver el ejemplo inmediatamente anterior para tratar de averiguar cómo obtener los miembros enum:

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

Esto es lo que se me ocurrió:

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

Valores de los miembros

Una vez que tenemos los nombres, podemos recorrerlos para obtener el valor correspondiente haciendo:

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

Clase de extensión

Creo que la mejor manera de hacer esto es crear sus propias funciones (ej. EnumEx.getNames(MyEnum)). No puede agregar una función a una enumeración.

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

Curiosamente (porque muchas personas han votado a favor de esta respuesta), no puedo hacer que esto funcione en TS Playground: shorturl.me/jJ8G2t ¿Estoy haciendo algo mal?
Peter

1
@Peter Actualicé la respuesta para incluir información sobre enumeraciones de cadenas. Además, querrá usar una for ofdeclaración en lugar de unafor in
David Sherret

24

Con TypeScript> = 2.4 puede definir enumeraciones de cadena:

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

Salida de 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 = {}));

Que es un objeto como este:

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

Por lo tanto, en el caso de enumeraciones de cadenas, no es necesario filtrar cosas, Object.keys(Color)y Object.values(Color)(*) son suficientes:

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

Ver ejemplo en línea en el patio de juegos de TypeScript

(*) Polyfill necesario para navegadores antiguos, consulte https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values#Browser_compatibility


Esto tiene el errorElement implicitly has an 'any' type because expression of type 'string' can't be used to index type 'typeof Color'. No index signature with a parameter of type 'string' was found on type 'typeof Color'.
Jonas

1
@Jonas Lo he arreglado con un yeso:Object.keys(Color) as (keyof typeof Color)[]
tanguy_k

genial, gracias!
Jonas

9

Puede agregar funciones para obtener los nombres y los índices de la enumeración:

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]

Tenga en cuenta que podría exportar las consts _namesy en _indiceslugar de exponerlas a través de una función exportada, pero debido a que los miembros exportados son miembros de la enumeración, podría decirse que es más claro tenerlos como funciones para que no se confundan con los miembros reales de la enumeración.

Sería bueno si TypeScript generara algo como esto automáticamente para todas las enumeraciones.


7

No existe un concepto de RTTI (información de tipo de tiempo de ejecución) en TypeScript (piense: reflexión), por lo que para hacer esto, se requiere conocimiento del JavaScript transpilado. Entonces, asumiendo TypeScript 0.95:

enum MyEnum {
    First, Second, Third
}

se convierte en:

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

Entonces, esto se modela como un objeto regular en javascript, donde MyEnum.0 == "First"y MyEnum.First == 0. Entonces, para enumerar todos los nombres de enumeración, debe obtener todas las propiedades que pertenecen al objeto y que tampoco son números:

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

Ok, ahora que te he dicho cómo hacerlo, puedo decirte que es una mala idea . No está escribiendo un idioma administrado, por lo que no puede incorporar estos hábitos. Todavía es simplemente JavaScript antiguo. Si quisiera usar una estructura en JavaScript para completar algún tipo de lista de opciones, usaría una matriz antigua simple. Una enumeración no es la elección correcta aquí, juego de palabras. El objetivo de TypeScript es generar JavaScript bonito e idiomático. El uso de enumeraciones de esta manera no preserva este objetivo.


5

Usé la solución propuesta por David Sherret y escribí una biblioteca npm que puede usar con el nombre enum-values...

Git: valores de enumeración

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

Una línea de una sola línea para obtener una lista de entradas (objetos / pares clave-valor):

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

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
Esto solo funciona si su enumeración no define ningún valor (está bien empaquetado y es incremental). Los valores de enumeración pueden ser "First = 0x1000" o "PageNotFound = 404", por ejemplo. NUM_OF_ENUMS siempre será uno mayor que el valor definido más grande, entonces 0x1001 o 405 en mis ejemplos.
Aku

2

Si desea asociar valores de cadenas a su enumeración, estos métodos no funcionan. Para tener una función genérica puedes hacer:

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

Funciona porque TypeScript agregará claves en el primer paso y valores en el segundo.

En TypeScript es:

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

1

La respuesta de Joe me hizo darme cuenta de que es mucho más fácil confiar en las primeras N teclas numéricas que hacer pruebas más complejas:

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
Esta es una suposición peligrosa porque es posible definir los valores asignados a las enumeraciones y no es necesario que sean incrementales y estén bien empaquetados. No es raro ver máscaras de bits en una enumeración, por ejemplo, o tal vez una tabla de códigos de error HTML que comienzan en 400.
Aku


0

para nodejs:

const { isNumber } = require('util');

Object.values(EnumObject)
      .filter(val => isNumber(val))
      .map(val => {
         // do your stuff
      })

0

Iterando sobre una enumeración

Las enumeraciones de cadena se utilizan mejor para esto. Aquí hay un ejemplo:

// 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" 
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.