typescript - Usando `keyof typeof enum` como parámetro de una función
Me he encontrado con este problema que no consigo resolver en TS. Y me encantaría que alguien pudiera señalarme la dirección correcta. Tengo una API que devuelve una serie de valores de enumeración, y para poder trabajar con ellos los modelé en Typescript de la siguiente manera:
enum condition = {NEW, USED}Pero al intentar trabajar con datos de la API necesito indicarlos como typeof keyof condition, y acceder a condition[0] (en este caso equivalente a condition[condition[NEW]] lo que conduce al mensaje de error
Argument of type 'string' is not assignable to parameter of type '"NEW" | "USED"'.(2345)Typescript exporta el objeto de condición como
var condition;
(function (condition) {
condition[condition["NEW"] = 0] = "NEW";
condition[condition["USED"] = 1] = "USED";
})(condition || (condition = {}));
;
Lo que significa que condition.NEW es 0 y condition[0] es NEW. Intenté forzar el tipo pasándolo con as keyof typeof condition así:
enum condition {NEW, USED};
function test(param: keyof typeof condition) {
console.log(param);
}
test(condition[condition.NEW]); // Fails linting, should pass
test(condition[condition.NEW] as keyof typeof condition); // Lint success
test('a' as keyof typeof condition); // Lint success, should fail
(Enlace al área de práctica: Playground )
Pero esto parece un truco en el mejor de los casos, ya que esencialmente ignorará el tipo que se le está pasando. Me preocuparía que ahora pasar una cadena no válida no se detecte correctamente. ¿Cómo puedo hacer que TS marque test(condition[condition.NEW]); como válido y test('a' as keyof typeof condition); como no válido?
Respuesta #1
Te has encontrado con una característica faltante de TypeScript, solicitada en microsoft/TypeScript#38806 y microsoft/TypeScript#50933. A los enums numéricos se les dan mapeos inversos donde puedes indexar en el objeto enum con el valor numérico del enum y obtener de vuelta la clave de cadena correspondiente. Pero el sistema de tipos no codifica este mapeo inverso de manera muy fuerte; en lugar de los pares valor-clave específicos, simplemente le da al objeto enum una firma de índice numérico cuyo tipo de propiedad es string. Así que mientras que Condition[0] se evalúa como "NEW" en tiempo de ejecución, TypeScript solo sabe que es de tipo string, lo cual no es suficiente para tus propósitos.
Hasta que no se implementen tipados inversos más fuertes, necesitarás buscar una solución alternativa. Siempre podrías escribir tu propia función de utilidad que proporcione tales tipados:
function strongReverseEnumMapping<const T extends Record<keyof T, PropertyKey>>(e: T) {
return e as
{ readonly [K in string & keyof T]: T[K] } &
{ readonly [K in keyof T as T[K] extends number ? T[K] : never]: K };
}
Aquí strongReverseEnumMapping() simplemente devuelve su entrada en tiempo de ejecución, pero el tipo se afirma como más fuerte, como una intersección de la parte del objeto enum donde los valores numéricos se reasignan a claves y las claves se reasignan a valores.
Luego puedes definir el renombramiento del enum original fuera del camino y pasarlo a la función auxiliar para obtener un enum con tipado más fuerte:
enum _Condition { NEW, USED };
const Condition = strongReverseEnumMapping(_Condition);
/* const Condition: {
readonly NEW: _Condition.NEW;
readonly USED: _Condition.USED;
} & {
readonly 0: "NEW";
readonly 1: "USED";
} */
type Condition = _Condition;
Ahora Condition se sabe que tiene "NEW" en la clave 0 y "USED" en la clave 1, y tu operación Condition[Condition.NEW] funciona como esperas:
const c = Condition[Condition.NEW]
// ^? const c: "NEW";
Enlace de Playground al código
Respuesta #2
Suponiendo que la API use cadenas para expresar los valores del enum en lugar de números, probablemente sea mejor simplemente definir un enum de cadenas:
enum Condition {
NEW = 'NEW',
USED = 'USED'
};
function test(param: Condition) {
console.log(param);
}
test(Condition.NEW);
test(Condition[Condition.NEW]);
test(Condition['NEW']);
