Wie kann ein Aufzählungstyp programmgesteuert aufgelistet werden?

86

Sagen , dass ich ein Typoskript haben enum, MyEnumwie folgt:

enum MyEnum {
    First,
    Second,
    Third
}

Was wäre der beste Weg in TypeScript 0.9.5, um ein Array der enumWerte zu erstellen ? Beispiel:

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

Antworten:

190

Dies ist die JavaScript-Ausgabe dieser Aufzählung:

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

Welches ist ein Objekt wie dieses:

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

Enum-Mitglieder mit String-Werten

TypeScript 2.4 hat die Möglichkeit hinzugefügt, dass Enums möglicherweise Werte für String-Enum-Mitglieder haben. Es ist also möglich, eine Aufzählung zu erhalten, die wie folgt aussieht:

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

Mitgliedsnamen abrufen

Wir können uns das Beispiel direkt oben ansehen, um herauszufinden, wie man die Enum-Mitglieder erhält:

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

Folgendes habe ich mir ausgedacht:

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

Mitgliedswerte

Sobald wir die Namen haben, können wir sie durchlaufen, um den entsprechenden Wert zu erhalten, indem wir Folgendes tun:

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

Erweiterungsklasse

Ich denke, der beste Weg, dies zu tun, besteht darin, eigene Funktionen zu erstellen (z. B. EnumEx.getNames(MyEnum)). Sie können einer Aufzählung keine Funktion hinzufügen.

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
quelle
Seltsamerweise (weil viele Leute diese Antwort positiv bewertet haben) kann ich das auf dem TS-Spielplatz nicht zum Laufen bringen : shorturl.me/jJ8G2t Mache ich etwas falsch?
Peter
1
@Peter Ich habe die Antwort aktualisiert, um Informationen zu Zeichenfolgenaufzählungen aufzunehmen. Außerdem möchten Sie eine for ofAnweisung anstelle vonfor in
David Sherret
24

Mit TypeScript> = 2.4 können Sie Zeichenfolgenaufzählungen definieren:

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

JavaScript ES5-Ausgabe:

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

Welches ist ein Objekt wie dieses:

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

Im Fall von String-Aufzählungen müssen also keine Dinge gefiltert werden, Object.keys(Color)und Object.values(Color)(*) reicht aus:

const colorKeys = Object.keys(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
*/

Siehe Online- Beispiel auf dem TypeScript-Spielplatz

(*) Polyfill für alte Browser erforderlich, siehe https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values#Browser_compatibility

tanguy_k
quelle
9

Sie können Funktionen hinzufügen, um die Namen und Indizes der Aufzählung abzurufen:

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]

Beachten Sie, dass Sie konnten nur die Export _namesund _indiceseher consts als sie durch eine exportierte Funktion ausgesetzt wird , sondern weil die ausgeführten Mitglieder Mitglieder der Enumeration sind , ist es wohl klarer sie als Funktionen zu haben , damit sie nicht den tatsächlichen Enum - Mitglieder sind verwirrt.

Es wäre schön, wenn TypeScript so etwas automatisch für alle Aufzählungen generieren würde.

Reich
quelle
7

Es gibt kein Konzept für RTTI (Runtime Type Information) in TypeScript (think: Reflection). Dazu sind Kenntnisse des transpilierten JavaScript erforderlich. Angenommen, TypeScript 0.95:

enum MyEnum {
    First, Second, Third
}

wird:

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

Dies wird also als reguläres Objekt in Javascript modelliert, wobei MyEnum.0 == "First"und MyEnum.First == 0. Um alle Aufzählungsnamen aufzulisten, müssen Sie alle Eigenschaften abrufen, die zum Objekt gehören und auch keine Zahlen sind:

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

Ok, jetzt habe ich dir gesagt, wie es geht. Ich darf dir sagen, dass dies eine schlechte Idee ist . Sie schreiben keine verwaltete Sprache, daher können Sie diese Gewohnheiten nicht mitbringen. Es ist immer noch einfach nur altes JavaScript. Wenn ich eine Struktur in JavaScript verwenden möchte, um eine Auswahlliste zu füllen, würde ich ein einfaches altes Array verwenden. Eine Aufzählung ist hier nicht die richtige Wahl, Wortspiel beabsichtigt. Das Ziel von TypeScript ist es, idiomatisches, hübsches JavaScript zu generieren. Die Verwendung von Aufzählungen auf diese Weise bewahrt dieses Ziel nicht.

x0n
quelle
5

Ich habe die von David Sherret vorgeschlagene Lösung verwendet und eine npm-Bibliothek geschrieben, die Sie mit dem Namen enum-values...

Git: Enum-Werte

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

Ein Einzeiler zum Abrufen einer Liste von Einträgen (Schlüsselwertobjekte / -paare):

Object.keys(MyEnum).filter(a=>a.match(/^\D/)).map(name=>({name, value: MyEnum[name] as number}));
Venryx
quelle
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
quelle
5
Dies funktioniert nur, wenn Ihre Aufzählung keine Werte definiert (sie ist gut gepackt und inkrementell). Aufzählungswerte können beispielsweise "First = 0x1000" oder "PageNotFound = 404" sein. NUM_OF_ENUMS ist immer eins größer als der größte definierte Wert, also 0x1001 oder 405 in meinen Beispielen.
Aku
2

Wenn Sie Ihrer Aufzählung Zeichenfolgenwerte zuordnen möchten, funktionieren diese Methoden nicht. Um eine generische Funktion zu haben, können Sie Folgendes tun:

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

Dies funktioniert, da TypeScript im ersten Schritt Schlüssel und im zweiten Schritt Werte hinzufügt.

In TypeScript ist 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);
Chklang
quelle
1

Die Antwort von Joe hat mir nur klar gemacht, dass es viel einfacher ist, sich auf die ersten N Zifferntasten zu verlassen, als komplexere Tests durchzuführen:

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
quelle
4
Dies ist eine gefährliche Annahme, da die den Aufzählungen zugewiesenen Werte definiert werden können und sie nicht inkrementell und gut verpackt sein müssen. Es ist nicht ungewöhnlich, Bitmasken beispielsweise in einer Aufzählung oder einer Tabelle mit HTML-Fehlercodes zu sehen, die bei 400 beginnen.
Aku
-1

für nodejs:

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

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