2015-06-08 4 views
17

Ich mag würde nennen eine ENUM-ähnliche Struktur in JS definieren, hat aber zwei Voraussetzungen:ES6 read-only Aufzählungen, den Wert zuordnen kann

  1. Die Werte nur gelesen werden, dh keine Benutzer zuordnen zu ihnen.
  2. Die Werte (0, 1, 2, ...) zurück in die Namen zugeordnet werden (wie bei Java name method)

Die Methoden Ich weiß Aufzählungen wie diese erstellen typischerweise eine Anforderung erfüllen oder die andere, nicht beide.

Ich habe versucht:

const MyEnum = { 
    a: 0, 
    b: 1, 
    c: 2 
}; 

ENUM selbst konstant ist, aber die Werte sind immer noch wandelbar und ich kann nicht Werte abbilden zurück effizient Namen.

Wenn ein enum in Typoskript zu schreiben, gibt sie:

var MyEnum; 
(function (MyEnum) { 
    MyEnum[MyEnum["a"] = 0] = "a"; 
    MyEnum[MyEnum["b"] = 1] = "b"; 
    MyEnum[MyEnum["c"] = 2] = "c"; 
})(MyEnum || (MyEnum = {})); 

Diese beiden Möglichkeiten abbilden kann, aber immer noch nicht konstante Werte hat.

Die einzige Option, die ich gefunden habe, die auf eine Klasse mit Getter würde beide Anforderungen erfüllt:

class MyEnum { 
    get a() { 
    return 0; 
    } 
    ... 
} 

Diese Methode dramatisch die rechtlichen Namen beschränkt und hat eine Menge Aufwand, vor allem in Browsern, die don‘ t inline getters gut (oder kann nicht).

@Shmiddty suggested freezing an object:

const MyEnum = Object.freeze({ 
    a: 0, 
    b: 1, 
    c: 2 
}); 

Dies erfüllt die ständige Anforderung gut, aber keine Werte eine gute Möglichkeit bieten zurück auf Namen abzubilden.

Ich kann einen Helfer schreiben, die die umgekehrte Abbildung wie baut:

function reverseEnum(enum) { 
    Object.keys(enum).forEach(k => { 
    enum[enum[k]] = k; 
    }); 
} 

Aber jede Art von programmatischer Lösung, um die umgekehrte Zuordnung zu generieren auf Probleme stoßen, wenn das ursprüngliche Objekt eingefroren ist oder auf andere Weise tatsächlich konstant.

Gibt es eine saubere, prägnante Lösung in JS?

+0

Warum nicht die Enumeration erstellen, umgekehrte Eigenschaften hinzufügen und * dann * einfrieren? Nichtsdestotrotz kann ich den Wunsch, dies prägnant zu tun, schätzen. – apsillers

+0

@apsillers, das ist eine ziemlich gute Idee. Definieren Sie ein normales const-enum-Objekt, und führen Sie dann einen Helfer aus, der die umgekehrte Zuordnung einrichtet und einfriert. Ich kann einen Anruf leicht genug aufrechterhalten, so dass das ziemlich gut funktioniert. Willst du es in eine Antwort verwandeln? – ssube

+0

Wird der spezifische Wert auch benötigt? Ich würde wahrscheinlich auch alle Werte mit Symbol symbolisieren, aber ich weiß nicht, was Sie erwarten. Normalerweise sind Enum-Werte nicht wirklich wichtig, zumindest in Code, den ich schreibe. – loganfsmyth

Antwort

10

Ich würde eine Map verwenden, so dass Ihre Enum-Werte von jedem Typ sein können, anstatt sie in Strings zu erzwingen.

function Enum(obj){ 
    const keysByValue = new Map(); 
    const EnumLookup = value => keysByValue.get(value); 

    for (const key of Object.keys(obj)){ 
     EnumLookup[key] = obj[key]; 
     keysByValue.set(EnumLookup[key], key); 
    } 

    // Return a function with all your enum properties attached. 
    // Calling the function with the value will return the key. 
    return Object.freeze(EnumLookup); 
} 

Wenn Ihr Enum alle Strings ist, würde ich wahrscheinlich auch eine Zeile ändern:

EnumLookup[key] = Symbol(obj[key]); 

, um sicherzustellen, dass die ENUM-Werte richtig verwendet werden. Wenn Sie nur einen String verwenden, haben Sie keine Garantie, dass ein Code nicht einfach einen normalen String übergeben hat, der zufällig mit einem Ihrer Enum-Werte übereinstimmt. Wenn Ihre Werte immer Zeichenfolgen oder Symbole sind, können Sie die Map auch für ein einfaches Objekt austauschen.

+0

Ich mag dies und es scheint gut mit Babel's Polyfill zu arbeiten für Karte. Was meinst du mit "wenn [mich] enum alle Saiten sind?" Die Schlüssel oder die Werte? – ssube

+0

'Symbol' erstellt einen neuen eindeutigen Wert, also ist es nur nützlich, wenn Sie sich nicht wirklich darum kümmern, was der Wert ist. Ich erwähne 'only strings', weil Sie eine Zeichenkette an' Symbol ('something') übergeben sollen, um das Debuggen zu erleichtern. In vielen Sprachen werden Enumerationswerte als undurchsichtige Werte behandelt. Wenn Sie eine Enumeration wie diese in JS verwenden, kann die Verwendung von Symbolen hilfreich sein. – loganfsmyth

18

This macht einen ziemlich guten Job, IMHO.

function Enum(a){ 
    let i = Object 
    .keys(a) 
    .reduce((o,k)=>(o[a[k]]=k,o),{}); 

    return Object.freeze(
    Object.keys(a).reduce(
     (o,k)=>(o[k]=a[k],o), v=>i[v] 
    ) 
); 
} // y u so terse? 

const FOO = Enum({ 
    a: 0, 
    b: 1, 
    c: "banana" 
}); 

console.log(FOO.a, FOO.b, FOO.c);   // 0 1 banana 
console.log(FOO(0), FOO(1), FOO("banana")); // a b c 

try { 
    FOO.a = "nope"; 
} 
catch (e){ 
    console.log(e); 
} 
+1

'// TODO: schreibe es5 äquivalent' – Shmiddty

+2

Ich brauchte eine Minute, um das zu lesen! – Shane

+0

Ich habe es nach unten coffescript mit der Ramda-Bibliothek portiert, die in es5 freundliche JS kompilieren sollte, während die nette Pfeil-Syntax für anonyme Funktionen – Shane

-1

Das gleiche wie von Shmiddty, aber ein wenig mehr wiederverwendbar dank Ramda, und ein wenig schöner Dank gegeben Coffee

R = require('ramda') 

lib = {} 

lib.reduceKeys = (obj) -> R.reduce R.__, R.__, R.keys obj 

lib.swapKeys = R.curry (obj, acc) -> 
    (lib.reduceKeys obj) ((acc, key) -> 
    acc[obj[key]] = key 
    acc 
), acc 

lib.copy = R.curry (obj, acc) -> 
    (lib.reduceKeys obj) ((acc, key) -> 
    acc[key] = obj[key] 
    acc 
), acc 

lib.Enum = (obj) -> 
    swapped = lib.swapKeys obj, {} 
    Object.freeze lib.copy obj, (val) -> swapped[val] 

exports[key] = val for own key, val of lib 
2

Erst kürzlich eine es6 Version implementiert, die recht gut funktioniert:

const k_VALUES = {} 

export class ErrorCode { 

    constructor(p_apiCode, p_httpCode){ 
     this.apiCode = p_apiCode; 
     this.httpCode = p_httpCode; 

     k_VALUES[p_apiCode] = this; 
    } 


    static create(p_apiCode){ 
     if(k_VALUES[p_apiCode]){ 
      return k_VALUES[p_apiCode]; 
     } 

     return ErrorCode.UNKNOWN; 
    } 
} 

ErrorCode.UNKNOWN     = new ErrorCode(0,  500); 
ErrorCode.NOT_FOUND    = new ErrorCode(-1000, 404); 
ErrorCode.NOT_FOUND_EMAIL   = new ErrorCode(-1001, 404); 
ErrorCode.BAD_REQUEST    = new ErrorCode(-1010, 404); 

Ich wollte ein ähnliches Muster wie das, was wir mit Java-Enums machen, implementieren. Dadurch kann ich einen Konstruktor verwenden, um Werte zu übergeben. Der Konstruktor friert dann das ErrorCode-Objekt ein - nett und bequem.

Verbrauch: zuerst Ihre Enum-Klasse importieren ...

import {ErrorCode} from "../common/services/errors/ErrorCode"; 

Jetzt, nach der ENUM-Klasse zu importieren, den Zugang es etwa so:

if(errCode.includes(ErrorCode.BAD_REQUEST.apiCode)){...} 

PS> Dies wird in Verbindung mit einem verwendet Webpack-Setup mit Babel, um unsere ES6-Klassen für die Browser-Kompatibilität zu konvertieren.