Búsqueda de sitios web

javascript - ¿Cómo obtener todos los valores de un enum a partir de un solo valor de enum en typescript?


Aquí hay una definición de enum

enum MyNumber {
    ONE='ONE',
    TWO='TWO',
    THREE='THREE',
    FOUR='FOUR',
    FIVE='FIVE',
}

Esto imprimirá todos los valores del enum.

const values = Object.values(MyNumber);
console.log(values); // prints ["ONE", "TWO", "THREE", "FOUR", "FIVE"]

Si solo se me da una variable myVar, sé que es un valor de un enum, pero no sé cuál es el enum real. ¿Hay alguna manera de inferir el tipo de enum y obtener todos los valores posibles del enum a partir de esta variable myVar?

Respuesta #1

Los enums son una característica exclusiva de TypeScript, con lo que quiero decir que no hay una definición formal de un enum en JavaScript. En tiempo de compilación, el enum en sí se elimina, y todo lo que queda (para enums no constantes) es un objeto simple; por ejemplo...

var MyNumber;
(function (MyNumber) {
    MyNumber["ONE"] = "ONE";
    MyNumber["TWO"] = "TWO";
    MyNumber["THREE"] = "THREE";
    MyNumber["FOUR"] = "FOUR";
    MyNumber["FIVE"] = "FIVE";
})(MyNumber || (MyNumber = {}));

Notarás allí que el objeto en sí mismo, por supuesto, tiene un tipo (MyNumber), pero los miembros de MyNumber no son del tipo MyNumber, y esta es la razón por la que no puedes identificar el tipo de un enum a partir de uno de sus miembros.

Nota: ¡Los enums en TypeScript (e incluso en C#) apestan! Son solo valores con nombre, a diferencia de Java, donde son objetos completamente calificados con semántica de enum.

Lo que podrías hacer es implementar una clase enum, en lugar de usar el enum incorporado de TypeScript. He visto que esta práctica se utiliza bastante en la comunidad de C#.

Aquí hay una clase abstracta para implementar enumeraciones...

abstract class Enum {

    protected constructor(public readonly value: number, public readonly name: string) {
    }
    
    public toString(): string {
        return this.name;
    }
    
    public valueOf(): number {
        return this.value;
    }

    public static getAll<T extends Enum>(): T[] {
        const enumeration: {[key:string]:any} = this;
        return Object
            .keys(enumeration)
            .filter(name => enumeration[name] instanceof this)
            .map(name => enumeration[name]);
    }
}

Y aquí es cómo lo implementarías...

class MyNumber extends Enum {
    public static readonly ONE: MyNumber = new MyNumber(1, "ONE");
    public static readonly TWO: MyNumber = new MyNumber(2, "TWO");
    public static readonly THREE: MyNumber = new MyNumber(3, "THREE");
    public static readonly FOUR: MyNumber = new MyNumber(4, "FOUR");
    public static readonly FIVE: MyNumber = new MyNumber(5, "FIVE");

    // Make sure this is private so consumers can't add to or edit your enum!
    private constructor(public readonly value: number, public readonly name: string) {
        super(value, name);
    }
}

Y aquí hay una prueba...

const value = MyNumber.ONE;
const proto: typeof Enum = value.constructor as typeof Enum;

console.log(proto.name);
console.log(proto.getAll());

Y la salida...

"MyNumber" 

[MyNumber: {
  "value": 1,
  "name": "ONE"
}, MyNumber: {
  "value": 2,
  "name": "TWO"
}, MyNumber: {
  "value": 3,
  "name": "THREE"
}, MyNumber: {
  "value": 4,
  "name": "FOUR"
}, MyNumber: {
  "value": 5,
  "name": "FIVE"
}] 

Y el patio de recreo: Patio de recreo

Nota: Hay algunos beneficios adicionales con esto; por ejemplo, actualmente en TypeScript, no se puede hacer T extends enum, pero con este enfoque, se puede hacer T extends Enum.

Respuesta #2

Cuando se trata específicamente de los enums de TypeScript, no es posible saber de qué tipo de enum proviene un valor. Esto se debe a que los valores de un enum se codifican internamente como números o cadenas, y no mantienen ninguna referencia a su fuente de enum.

Esto es más evidente al entender que un enum (no constante) se compila a un objeto que mapea de claves a valores y viceversa.

enum MyNumber {
    ONE = 'ONE',
    TWO = 'TWO',
    THREE = 'THREE',
    FOUR = 'FOUR',
    FIVE = 'FIVE',
}
var MyNumber;
(function (MyNumber) {
    MyNumber["ONE"] = "ONE";
    MyNumber["TWO"] = "TWO";
    MyNumber["THREE"] = "THREE";
    MyNumber["FOUR"] = "FOUR";
    MyNumber["FIVE"] = "FIVE";
})(MyNumber || (MyNumber = {}));

Incluso si el tipo de miembro se conoce en tiempo de compilación, el compilador actualmente no proporciona un mapeo de tipo de un miembro de enumeración a su tipo de enumeración respectivo, a partir de TypeScript v4.8.

Si uno realmente necesita señalar de qué enumeración provino, entonces uno debe codificar esa información junto con el valor de la enumeración, o usar clases en lugar de enumeraciones.