¿Existe un "valor de" similar a la "clave de" en TypeScript?
Quiero poder asignar una propiedad de objeto a un valor dada una clave y un valor como entradas y aún así poder determinar el tipo de valor. Es un poco difícil de explicar, por lo que este código debería revelar el problema:
type JWT = { id: string, token: string, expire: Date };
const obj: JWT = { id: 'abc123', token: 'tk01', expire: new Date(2018, 2, 14) };
function print(key: keyof JWT) {
switch (key) {
case 'id':
case 'token':
console.log(obj[key].toUpperCase());
break;
case 'expire':
console.log(obj[key].toISOString());
break;
}
}
function onChange(key: keyof JWT, value: any) {
switch (key) {
case 'id':
case 'token':
obj[key] = value + ' (assigned)';
break;
case 'expire':
obj[key] = value;
break;
}
}
print('id');
print('expire');
onChange('id', 'def456');
onChange('expire', new Date(2018, 3, 14));
print('id');
print('expire');
onChange('expire', 1337); // should fail here at compile time
print('expire'); // actually fails here at run time
Intenté cambiar value: any
a value: valueof JWT
pero no funcionó.
Idealmente, onChange('expire', 1337)
fallaría porque 1337
no es un tipo de fecha.
¿ Cómo puedo cambiar value: any
para que sea el valor de la clave dada?
ACTUALIZACIÓN: Parece que el título de la pregunta atrae a personas que buscan una unión de todos los tipos de valores de propiedad posibles, de manera análoga a la forma en que keyof
le brinda la unión de todos los tipos de claves de propiedad posibles. Ayudemos a esa gente primero. Puedes hacer algo ValueOf
análogo a keyof
, usando tipos de acceso indexados como keyof T
clave, así:
type ValueOf<T> = T[keyof T];
que te da
type Foo = { a: string, b: number };
type ValueOfFoo = ValueOf<Foo>; // string | number
Para la pregunta indicada, puede usar claves individuales, más estrechas que keyof T
, para extraer solo el tipo de valor que le interesa:
type sameAsString = Foo['a']; // look up a in Foo
type sameAsNumber = Foo['b']; // look up b in Foo
Para asegurarse de que el par clave/valor "coincida" correctamente en una función, debe utilizar tipos de acceso genéricos además de indexados, como este:
declare function onChange<K extends keyof JWT>(key: K, value: JWT[K]): void;
onChange('id', 'def456'); // okay
onChange('expire', new Date(2018, 3, 14)); // okay
onChange('expire', 1337); // error. 1337 not assignable to Date
La idea es que el key
parámetro permita al compilador inferir el K
parámetro genérico. Luego requiere que value
coincida JWT[K]
con el tipo de acceso indexado que necesita.
Hay otra forma de extraer el tipo de unión del objeto:
const myObj = {
a: 1,
b: 'some_string'
} as const;
type Values = typeof myObj[keyof typeof myObj];
El tipo de unión de resultados para Values
es1 | "some_string"
Es posible gracias a las afirmaciones constantes ( as const
parte) introducidas en TS 3.4.
Si alguien todavía busca la implementación de valueof
para algún propósito, se me ocurrió esta:
type valueof<T> = T[keyof T]
Uso:
type actions = {
a: {
type: 'Reset'
data: number
}
b: {
type: 'Apply'
data: string
}
}
type actionValues = valueof<actions>
Funciona como se esperaba :) Devuelve una Unión de todos los tipos posibles
Con la siguiente función puede limitar el valor para que sea el de esa clave en particular.
function setAttribute<T extends Object, U extends keyof T>(obj: T, key: U, value: T[U]) {
obj[key] = value;
}
Ejemplo
interface Pet {
name: string;
age: number;
}
const dog: Pet = { name: 'firulais', age: 8 };
setAttribute(dog, 'name', 'peluche') <-- Works
setAttribute(dog, 'name', 100) <-- Error (number is not string)
setAttribute(dog, 'age', 2) <-- Works
setAttribute(dog, 'lastname', '') <-- Error (lastname is not a property)