¿Existe un "valor de" similar a "clave de" en TypeScript?


119

Quiero poder asignar una propiedad de objeto a un valor dado 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é cambiarme value: anya value: valueof JWTpero no funcionó.

Idealmente, onChange('expire', 1337)fallaría porque 1337no es un tipo de fecha.

¿Cómo puedo cambiar value: anypara que sea el valor de la clave dada?

Respuestas:


221

ACTUALIZACIÓN: Parece que el título de la pregunta atrae a las personas que buscan una unión de todos los tipos de valores de propiedad posibles, de forma análoga a la forma en que keyofle brinda la unión de todos los tipos de claves de propiedad posibles. Ayudemos a esas personas primero. Puede hacer un ValueOfanálogo a keyof, usando tipos de búsqueda con keyof Tcomo 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 como se indica, puede usar claves individuales, más estrechas que keyof T, para extraer solo el tipo de valor que le interesa:

type sameAsString = Foo['a']; // lookup a in Foo
type sameAsNumber = Foo['b']; // lookup b in Foo

Para asegurarse de que el par clave / valor "coincida" correctamente en una función, debe usar genéricos y tipos de búsqueda, 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 keyparámetro permita al compilador inferir el Kparámetro genérico . Entonces requiere que valuecoincida JWT[K], el tipo de búsqueda que necesita.

Espero que ayude; ¡buena suerte!


Quiero crear un tupledesde valores. ¿Hay alguna posibilidad de hacer eso?
— Morteza Tourani

Eso suena como una pregunta no relacionada, por lo que probablemente debería hacerla en otro lugar, con un ejemplo mínimo reproducible para que la gente esté segura de entender su caso de uso. Buena suerte.
— jcalz

1
Me encontré con un problema al usar una enumeración con valores de cadena con miembros de función. Para manejar esto bien, puede usar type StringValueOf<T> = T[keyof T] & string;. Los mejores documentos que encontré sobre enumeraciones de cadenas son las notas de la versión de TypeScript 2.9
— karmakaze

41

Si alguien todavía busca la implementación valueofpara cualquier propósito, este es uno que se me ocurrió:

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


2
Esto es bonito. Casi resuelve mi otro problema . Sin embargo, el interruptor no parece reducir el tipo en absoluto.
— styfle

Quiero crear un tupledesde valores. ¿Hay alguna posibilidad de hacer eso?
— Morteza Tourani

12

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];

Resultado: 1 | "some_string"


Esto constes muy valioso, TypeScript realmente proporcionará los valores y eliminará los duplicados; Es muy bueno para los diccionarios.
— Noam

Exactamente lo que estaba buscando !
— Mavrick RMX '21 de

2

Gracias las respuestas existentes que resuelven el problema a la perfección. Solo quería agregar una biblioteca que incluye este tipo de utilidad, si prefiere importar esta común.

https://github.com/piotrwitek/utility-types#valuestypet

import { ValuesType } from 'utility-types';

type Props = { name: string; age: number; visible: boolean };
// Expect: string | number | boolean
type PropsValues = ValuesType<Props>;

2

Prueba esto:

type ValueOf<T> = T extends any[] ? T[number] : T[keyof T]

Funciona en una matriz o en un objeto simple.

// type TEST1 = boolean | 42 | "heyhey"
type TEST1 = ValueOf<{ foo: 42, sort: 'heyhey', bool: boolean }>
// type TEST2 = 1 | 4 | 9 | "zzz..."
type TEST2 = ValueOf<[1, 4, 9, 'zzz...']>

0

Un trazador de líneas:

type ValueTypesOfPropFromMyCoolType = MyCoolType[keyof MyCoolType];

Ejemplo de un método genérico:

declare function doStuff<V extends MyCoolType[keyof MyCoolType]>(propertyName: keyof MyCoolType, value: V) => void;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.