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

Resuelto styfle asked hace 6 años • 12 respuestas

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: 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?

styfle avatar Mar 15 '18 02:03 styfle
Aceptado

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 keyofle brinda la unión de todos los tipos de claves de propiedad posibles. Ayudemos a esa gente primero. Puedes hacer algo ValueOfanálogo a keyof, usando tipos de acceso indexados como keyof Tclave, 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 keyparámetro permita al compilador inferir el Kparámetro genérico. Luego requiere que valuecoincida JWT[K]con el tipo de acceso indexado que necesita.

jcalz avatar Mar 14 '2018 19:03 jcalz

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 Valueses1 | "some_string"

Es posible gracias a las afirmaciones constantes ( as constparte) introducidas en TS 3.4.

Dima avatar Feb 10 '2020 10:02 Dima

Si alguien todavía busca la implementación de valueofpara 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

Chris Kowalski avatar Mar 29 '2018 04:03 Chris Kowalski

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)
Jose Gomez avatar May 05 '2021 00:05 Jose Gomez