typescript generics enums

typescript - ¿Tipo genérico para obtener claves de enumeración como cadena de unión en mecanografía?



generics enums (1)

No, el consumidor deberá usar typeof MyEnum para referirse al objeto cuyas claves son A , B y C

MUCHA EXPLICACIÓN A CONTINUACIÓN, ALGUNAS DE LAS QUE PROBABLEMENTE YA SABES

Como probablemente sepa, TypeScript agrega un sistema de tipo estático a JavaScript, y ese sistema de tipo se borra cuando se transpila el código. La sintaxis de TypeScript es tal que algunas expresiones y declaraciones se refieren a valores que existen en tiempo de ejecución, mientras que otras expresiones y declaraciones se refieren a tipos que existen solo en el momento del diseño / compilación. Los valores tienen tipos, pero no son tipos en sí mismos. Es importante destacar que hay algunos lugares en el código donde el compilador espera un valor e interpreta la expresión que encuentra como un valor si es posible, y otros lugares donde el compilador espera un tipo e interpreta la expresión que encuentra como un tipo si es posible.

Al compilador no le importa o se confunde si es posible que una expresión se interprete como un valor y un tipo. Es perfectamente feliz, por ejemplo, con los dos sabores de null en el siguiente código:

let maybeString: string | null = null;

La primera instancia de null es un tipo y la segunda es un valor. Tampoco tiene problema con

let Foo = {a: 0}; type Foo = {b: string};

donde el primer Foo es un valor con nombre y el segundo Foo es un tipo con nombre. Tenga en cuenta que el tipo del valor Foo es {a: number} , mientras que el tipo Foo es {b: string} . Ellos no son los mismos.

Incluso el tipo de operador lleva una doble vida. La expresión typeof x siempre espera que x sea ​​un valor , pero typeof x sí podría ser un valor o tipo dependiendo del contexto:

let bar = {a: 0}; let TypeofBar = typeof bar; // the value "object" type TypeofBar = typeof bar; // the type {a: number}

La línea let TypeofBar = typeof bar; lo hará a través de JavaScript, y utilizará el operador de tipo de JavaScript en el tiempo de ejecución y producirá una cadena. Pero type TypeofBar = typeof bar ; se borra y utiliza el operador de consulta de tipo TypeScript para examinar el tipo estático que TypeScript ha asignado al valor denominado bar .

Ahora, la mayoría de las construcciones de lenguaje en TypeScript que introducen nombres crean un valor con nombre o un tipo con nombre. Aquí hay algunas introducciones de valores con nombre:

const value1 = 1; let value2 = 2; var value3 = 3; function value4() {}

Y aquí están algunas introducciones de tipos nombrados:

interface Type1 {} type Type2 = string;

Pero hay algunas declaraciones que crean tanto un valor con nombre como un tipo con nombre, y, como Foo arriba, el tipo del valor con nombre no es el tipo con nombre . Los grandes son class y enum :

class Class { public prop = 0; } enum Enum { A, B }

Aquí, el tipo Class es el tipo de una instancia de Class , mientras que el valor Class es el objeto constructor . Y typeof Class no es Class :

const instance = new Class(); // value instance has type (Class) // type (Class) is essentially the same as {prop: number}; const ctor = Class; // value ctor has type (typeof Class) // type (typeof Class) is essentially the same as new() => Class;

Y, el tipo Enum es el tipo de un elemento de la enumeración; Una unión de los tipos de cada elemento. Mientras que el valor Enum es un objeto cuyas claves son A y B , y cuyas propiedades son los elementos de la enumeración. Y typeof Enum no es Enum :

const element = Math.random() < 0.5 ? Enum.A : Enum.B; // value element has type (Enum) // type (Enum) is essentially the same as Enum.A | Enum.B // which is a subtype of (0 | 1) const enumObject = Enum; // value enumObject has type (typeof Enum) // type (typeof Enum) is essentially the same as {A: Enum.A; B: Enum.B} // which is a subtype of {A:0, B:1}

Retrocediendo camino hasta su pregunta ahora. Quieres inventar un operador de tipo que funcione así:

type KeysOfEnum = EnumKeysAsStrings<Enum>; // "A" | "B"

donde coloca el tipo Enum y obtiene las claves del objeto Enum . Pero como se ve arriba, el tipo Enum no es el mismo que el objeto Enum . Y desafortunadamente el tipo no sabe nada sobre el valor. Es algo así como decir esto:

type KeysOfEnum = EnumKeysAsString<0 | 1>; // "A" | "B"

Claramente, si lo escribe así, verá que no hay nada que pueda hacer para el tipo 0 | 1 0 | 1 que produciría el tipo "A" | "B" "A" | "B" . Para que funcione, deberías pasarle un tipo que sepa sobre el mapeo. Y ese tipo es typeof Enum ...

type KeysOfEnum = EnumKeysAsStrings<typeof Enum>;

que es como

type KeysOfEnum = EnumKeysAsString<{A:0, B:1}>; // "A" | "B"

que es posible ... si type EnumKeysAsString<T> = keyof T

Así que estás atascado haciendo que el consumidor especifique typeof Enum . ¿Hay soluciones? Bueno, tal vez podrías usar algo que haga que un valor, como una función?

function enumKeysAsString<TEnum>(theEnum: TEnum): keyof TEnum { // eliminate numeric keys const keys = Object.keys(theEnum).filter(x => (+x)+"" !== x) as (keyof TEnum)[]; // return some random key return keys[Math.floor(Math.random()*keys.length)]; }

Entonces puedes llamar

const someKey = enumKeysAsString(Enum);

y el tipo de someKey será "A" | "B" "A" | "B" . Sí, pero luego para usarlo como tipo tendrías que consultarlo:

type KeysOfEnum = typeof someKey;

lo que te obliga a usar typeof nuevo y es incluso más detallado que tu solución, especialmente porque no puedes hacer esto:

type KeysOfEnum = typeof enumKeysAsString(Enum); // error

Blegh Lo siento.

RECORDAR:

  • ESTO NO ES POSIBLE;
  • TIPOS Y VALORES BLAH BLAH;
  • TODAVÍA NO ES POSIBLE;
  • LO SIENTO.

Espero que tenga sentido. Buena suerte.

Considere la siguiente enumeración mecanografiada:

enum MyEnum { A, B, C };

Si quiero otro tipo que sea las cadenas unidas de las claves de esa enumeración, puedo hacer lo siguiente:

type MyEnumKeysAsStrings = keyof typeof MyEnum; // "A" | "B" | "C"

Esto es muy útil.

Ahora quiero crear un tipo genérico que funcione universalmente en las enumeraciones de esta manera, para que pueda decir:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<MyEnum>;

Me imagino la sintaxis correcta para eso sería:

type AnyEnumKeysAsStrings<TEnum> = keyof typeof TEnum; // TS Error: ''TEnum'' only refers to a type, but is being used as a value here.

Pero eso genera un error de compilación: "''TEnum'' solo se refiere a un tipo, pero aquí se usa como un valor".

Esto es inesperado y triste. De manera incompleta, puedo solucionarlo de la siguiente manera al eliminar el tipo de la parte derecha de la declaración del genérico y agregarlo al parámetro de tipo en la declaración del tipo específico:

type AnyEnumAsUntypedKeys<TEnum> = keyof TEnum; type MyEnumKeysAsStrings = AnyEnumAsUntypedKeys<typeof MyEnum>; // works, but not kind to consumer. Ick.

Sin embargo, no me gusta esta solución, porque significa que el consumidor debe recordar hacer esta especificación espinosa de typeof en el genérico.

¿Hay alguna sintaxis que me permita especificar el tipo genérico como quiero inicialmente, para ser amable con el consumidor?