Comment énumérer par programme un type d'énumération?

90

Dire que j'ai un tapuscrit enum, MyEnumcomme suit:

enum MyEnum {
    First,
    Second,
    Third
}

Quelle serait la meilleure façon dans TypeScript 0.9.5 de produire un tableau des enumvaleurs? Exemple:

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

Réponses:

192

Voici la sortie JavaScript de cette énumération:

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

Quel est un objet comme celui-ci:

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

Énumérer les membres avec des valeurs de chaîne

TypeScript 2.4 a ajouté la possibilité pour les enums d'avoir éventuellement des valeurs de membre enum de chaîne. Il est donc possible de se retrouver avec une énumération qui ressemble à ceci:

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

Obtenir les noms des membres

Nous pouvons regarder l'exemple ci-dessus pour essayer de comprendre comment obtenir les membres enum:

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

Voici ce que j'ai trouvé:

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

Valeurs des membres

Une fois que nous avons les noms, nous pouvons les parcourir pour obtenir la valeur correspondante en faisant:

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

Classe d'extension

Je pense que la meilleure façon de faire est de créer vos propres fonctions (ex. EnumEx.getNames(MyEnum)). Vous ne pouvez pas ajouter de fonction à une énumération.

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);
    }
}
David Sherret
la source
Curieusement (parce que beaucoup de gens ont voté pour cette réponse), je ne peux pas faire fonctionner cela dans le TS Playground: shorturl.me/jJ8G2t Est-ce que je fais quelque chose de mal?
Peter le
1
@Peter J'ai mis à jour la réponse pour inclure des informations sur les énumérations de chaînes. En outre, vous voudrez utiliser une for ofdéclaration au lieu d'unfor in
David Sherret
24

Avec TypeScript> = 2.4, vous pouvez définir des énumérations de chaînes:

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

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

Quel est un objet comme celui-ci:

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

Ainsi, dans le cas des énumérations de chaînes, pas besoin de filtrer les choses, Object.keys(Color)et Object.values(Color)(*) suffisent:

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

Voir l' exemple en ligne sur le terrain de jeu TypeScript

(*) Polyfill nécessaire pour les anciens navigateurs, voir https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values#Browser_compatibility

tanguy_k
la source
Cela a l'erreurElement 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 je l'ai corrigé avec un casting:Object.keys(Color) as (keyof typeof Color)[]
tanguy_k
super merci!
Jonas
9

Vous pouvez ajouter des fonctions pour obtenir les noms et les indices de l'énumération:

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]

Notez que vous pouvez simplement exporter les _nameset _indicesconsts plutôt que de les exposer via une fonction exportée, mais comme les membres exportés sont des membres de l'énumération, il est sans doute plus clair de les avoir en tant que fonctions afin qu'ils ne soient pas confondus avec les membres enum réels.

Ce serait bien si TypeScript générait automatiquement quelque chose comme ça pour toutes les énumérations.

Riches
la source
7

Il n'y a pas de concept de RTTI (informations de type d'exécution) dans TypeScript (pensez: réflexion) donc pour ce faire, la connaissance du JavaScript transpilé est nécessaire. Donc, en supposant TypeScript 0.95:

enum MyEnum {
    First, Second, Third
}

devient:

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

Donc, ceci est modélisé comme un objet régulier en javascript, où MyEnum.0 == "First"et MyEnum.First == 0. Donc, pour énumérer tous les noms d'énumération, vous devez obtenir toutes les propriétés qui appartiennent à l'objet et qui ne sont pas non plus des nombres:

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

Ok, alors maintenant je vous ai dit comment faire, j'ai le droit de vous dire que c'est une mauvaise idée . Vous n'écrivez pas une langue gérée, vous ne pouvez donc pas adopter ces habitudes. C'est toujours juste du vieux JavaScript. Si je voulais utiliser une structure en JavaScript pour remplir une sorte de liste de choix, j'utiliserais un tableau ancien. Une énumération n'est pas le bon choix ici, jeu de mots. Le but de TypeScript est de générer un joli JavaScript idiomatique. L'utilisation d'énumérations de cette manière ne préserve pas cet objectif.

x0n
la source
5

J'ai utilisé la solution proposée par David Sherret et j'ai écrit une bibliothèque npm que vous pouvez utiliser nommée 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);
Slava Shpitalny
la source
3

Une ligne unique pour obtenir une liste d'entrées (objets / paires clé-valeur):

Object.keys(MyEnum).filter(a=>a.match(/^\D/)).map(name=>({name, value: MyEnum[name] as number}));
Venryx
la source
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.
}
Joe
la source
5
Cela ne fonctionne que si votre énumération ne définit aucune valeur (elle est bien compressée et incrémentielle). Les valeurs d'énumération peuvent être "First = 0x1000" ou "PageNotFound = 404" par exemple. NUM_OF_ENUMS sera toujours un supérieur à la plus grande valeur définie, donc 0x1001 ou 405 dans mes exemples.
Aku
2

Si vous souhaitez associer des valeurs de chaînes à votre énumération, ces méthodes ne fonctionnent pas. Pour avoir une fonction générique, vous pouvez faire:

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

Cela fonctionne car TypeScript ajoutera des clés dans la première étape et des valeurs dans la deuxième étape.

Dans TypeScript c'est:

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);
Chklang
la source
1

La réponse de Joe m'a juste fait comprendre qu'il est beaucoup plus facile de s'appuyer sur les N premières touches numériques que de faire des tests plus complexes:

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))
kbtz
la source
4
C'est une hypothèse dangereuse car il est possible de définir les valeurs assignées aux énumérations et elles n'ont pas besoin d'être incrémentielles et bien compactées. Il n'est pas rare de voir des masques de bits dans une énumération par exemple, ou peut-être un tableau de codes d'erreur HTML commençant à 400.
Aku
0

pour nodejs:

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

Object.values(EnumObject)
      .filter(val => isNumber(val))
      .map(val => {
         // do your stuff
      })
Suben Saha
la source
0

Itérer sur une énumération

Les énumérations de chaînes sont mieux utilisées pour cela. Voici un exemple:

// 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" 
Willem van der Veen
la source