Bagaimana cara menghitung jenis enumerasi secara terprogram?

90

Katakanlah saya memiliki naskah enum, MyEnumsebagai berikut:

enum MyEnum {
    First,
    Second,
    Third
}

Apa cara terbaik dalam TypeScript 0.9.5 untuk menghasilkan larik enumnilai? Contoh:

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

Jawaban:

192

Ini adalah keluaran JavaScript dari enum itu:

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

Yang merupakan objek seperti ini:

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

Anggota Enum dengan Nilai String

TypeScript 2.4 menambahkan kemampuan enum untuk kemungkinan memiliki nilai anggota enum string. Jadi mungkin saja berakhir dengan enum yang terlihat seperti berikut:

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

Mendapatkan Nama Anggota

Kita dapat melihat contoh di atas untuk mencoba mencari cara mendapatkan anggota enum:

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

Inilah yang saya dapatkan:

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

Nilai Anggota

Setelah kita memiliki nama, kita dapat mengulanginya untuk mendapatkan nilai yang sesuai dengan melakukan:

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

Kelas Ekstensi

Saya pikir cara terbaik untuk melakukan ini adalah dengan membuat fungsi Anda sendiri (mis. EnumEx.getNames(MyEnum)). Anda tidak dapat menambahkan fungsi ke enum.

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
sumber
Anehnya (karena banyak orang telah memberi suara positif pada jawaban ini), saya tidak dapat mengaktifkan ini di TS Playground: shorturl.me/jJ8G2t Apakah saya melakukan kesalahan?
Peter
1
@Peter Saya memperbarui jawaban untuk memasukkan informasi tentang enum string. Selain itu, Anda sebaiknya menggunakan for ofpernyataan alih-alihfor in
David Sherret
24

Dengan TypeScript> = 2.4 Anda dapat menentukan enum string:

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

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

Yang merupakan objek seperti ini:

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

Jadi, dalam kasus enum string, tidak perlu memfilter, Object.keys(Color)dan Object.values(Color)(*) sudah cukup:

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

Lihat contoh online di taman bermain TypeScript

(*) Polyfill diperlukan untuk browser lama, lihat https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values#Browser_compatibility

tanguy_k
sumber
Ini memiliki kesalahanElement 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 Saya sudah memperbaikinya dengan pemeran:Object.keys(Color) as (keyof typeof Color)[]
tanguy_k
Terima kasih banyak!
Jonas
9

Anda dapat menambahkan fungsi untuk mendapatkan nama dan indeks enum:

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]

Perhatikan bahwa Anda bisa mengekspor _namesdan _indicesconsts daripada mengeksposnya melalui fungsi yang diekspor, tetapi karena anggota yang diekspor adalah anggota enum, maka lebih jelas untuk menjadikannya sebagai fungsi sehingga mereka tidak bingung dengan anggota enum yang sebenarnya.

Alangkah baiknya jika TypeScript menghasilkan sesuatu seperti ini secara otomatis untuk semua enum.

Kaya
sumber
7

Tidak ada konsep RTTI (informasi tipe runtime) di TypeScript (pikirkan: refleksi) jadi untuk melakukan ini, pengetahuan tentang JavaScript yang ditranspilasi diperlukan. Jadi, dengan asumsi TypeScript 0.95:

enum MyEnum {
    First, Second, Third
}

menjadi:

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

Jadi, ini dimodelkan sebagai objek biasa dalam javascript, where MyEnum.0 == "First"dan MyEnum.First == 0. Jadi, untuk menghitung semua nama enumerasi, Anda perlu mendapatkan semua properti milik objek dan yang juga bukan angka:

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

Oke, jadi sekarang saya sudah memberi tahu Anda cara melakukannya, saya diizinkan memberi tahu Anda bahwa ini adalah ide yang buruk . Anda tidak menulis bahasa yang diatur, jadi Anda tidak bisa membawa kebiasaan ini. Ini masih JavaScript lama biasa. Jika saya ingin menggunakan struktur di JavaScript untuk mengisi beberapa jenis daftar pilihan, saya akan menggunakan array lama biasa. Enum bukanlah pilihan yang tepat di sini, permainan kata-kata. Tujuan TypeScript adalah menghasilkan JavaScript yang idiomatis dan cantik. Menggunakan enum dengan cara ini tidak mempertahankan tujuan ini.

x0n
sumber
5

Saya menggunakan solusi yang diusulkan oleh David Sherret dan menulis perpustakaan npm yang dapat Anda gunakan bernama enum-values...

Git: nilai enum

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

Satu baris untuk mendapatkan daftar entri (objek / pasangan nilai kunci):

Object.keys(MyEnum).filter(a=>a.match(/^\D/)).map(name=>({name, value: MyEnum[name] as number}));
Venryx
sumber
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
sumber
5
Ini hanya berfungsi jika enum Anda tidak menentukan nilai apa pun (itu dikemas dengan baik dan inkremental). Misalnya, nilai enum mungkin "First = 0x1000" atau "PageNotFound = 404". NUM_OF_ENUMS akan selalu lebih besar dari nilai yang ditentukan terbesar, jadi 0x1001 atau 405 dalam contoh saya.
Aku
2

Jika Anda ingin mengaitkan nilai string ke enum, metode ini tidak berfungsi. Untuk memiliki fungsi generik, Anda dapat melakukan:

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

Ini berfungsi karena TypeScript akan menambahkan kunci di langkah pertama, dan nilai di langkah kedua.

Di TypeScript itu:

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
sumber
1

Jawaban joe baru saja membuat saya menyadari bahwa jauh lebih mudah mengandalkan kunci numerik N pertama daripada membuat pengujian yang lebih kompleks:

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.dll
sumber
4
Ini adalah asumsi yang berbahaya karena dimungkinkan untuk menentukan nilai yang ditetapkan ke enum dan tidak perlu inkremental dan dikemas dengan baik. Tidak jarang melihat bit mask dalam enum misalnya, atau mungkin tabel kode kesalahan HTML yang dimulai pada 400.
Aku
0

untuk nodejs:

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

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

Iterasi di atas enum

String Enum paling baik digunakan untuk ini. Berikut ini contohnya:

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