Y a-t-il une `valueof` similaire à` keyof` dans TypeScript?


119

Je veux pouvoir attribuer une propriété d'objet à une valeur donnée une clé et une valeur comme entrées tout en étant capable de déterminer le type de la valeur. C'est un peu difficile à expliquer, donc ce code devrait révéler le problème:

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

J'ai essayé de changer value: anypour value: valueof JWTmais cela ne fonctionne pas.

Idéalement, onChange('expire', 1337)échouerait car ce 1337n'est pas un type de date.

Comment puis-je changer value: anyla valeur de la clé donnée?

Réponses:


223

MISE À JOUR: On dirait que le titre de la question attire les personnes à la recherche d'une union de tous les types de valeurs de propriété possibles, de la même manière que keyofvous donne l'union de tous les types de clés de propriété possibles. Aidons d'abord ces gens. Vous pouvez faire un ValueOfanalogue à keyof, en utilisant des types de recherche avec keyof Tcomme clé, comme ceci:

type ValueOf<T> = T[keyof T];

qui te donne

type Foo = { a: string, b: number };
type ValueOfFoo = ValueOf<Foo>; // string | number

Pour la question comme indiqué, vous pouvez utiliser des clés individuelles, plus étroites que keyof T, pour extraire uniquement le type de valeur qui vous intéresse:

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

Afin de vous assurer que la paire clé / valeur "correspond" correctement dans une fonction, vous devez utiliser des génériques ainsi que des types de recherche, comme ceci:

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

L'idée est que le keyparamètre permet au compilateur de déduire le Kparamètre générique . Ensuite, il faut que les valuecorrespondances JWT[K], le type de recherche dont vous avez besoin.

J'espère que cela pourra aider; bonne chance!


Je veux créer tupledes valeurs à partir de. Y a-t-il une chance de le faire?
— Morteza Tourani

Cela ressemble à une question sans rapport, vous devriez donc probablement la poser ailleurs, avec un exemple minimal reproductible afin que les gens soient sûrs de comprendre votre cas d'utilisation. Bonne chance.
— jcalz

1
Ran dans un problème en utilisant une énumération à valeur de chaîne avec des membres de fonction. Pour bien gérer cela, vous pouvez utiliser type StringValueOf<T> = T[keyof T] & string;. Les meilleurs documents que j'ai trouvés sur les énumérations de chaînes sont les notes de publication de TypeScript 2.9
— Karmakaze

41

Si quelqu'un cherche toujours la mise en œuvre de valueofà quelque fin que ce soit, c'est celui que j'ai trouvé:

type valueof<T> = T[keyof T]

Usage:

type actions = {
  a: {
    type: 'Reset'
    data: number
  }
  b: {
    type: 'Apply'
    data: string
  }
}
type actionValues = valueof<actions>

Fonctionne comme prévu :) Renvoie une Union de tous les types possibles


2
C'est sympa. Cela résout presque mon autre problème , mais le commutateur ne semble pas du tout restreindre le type.
— styfle

Je veux créer tupledes valeurs à partir de. Y a-t-il une chance de le faire?
— Morteza Tourani

12

Il existe une autre façon d'extraire le type d'union de l'objet:

  const myObj = { a: 1, b: 'some_string' } as const;
  type values = typeof myObj[keyof typeof myObj];

Résultat: 1 | "some_string"


Cette constchose est très précieuse, TypeScript fournira réellement les valeurs elles-mêmes et supprimera les doublons; C'est très bon pour les dictionnaires.
— Noam

Exactement ce que je cherchais!
— Mavrick RMX '21

2

Merci les réponses existantes qui résolvent parfaitement le problème. Je voulais juste ajouter une bibliothèque a inclus ce type d'utilitaire, si vous préférez importer celui-ci commun.

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

Essaye ça:

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

Cela fonctionne sur un tableau ou un objet 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

Bon mot:

type ValueTypesOfPropFromMyCoolType = MyCoolType[keyof MyCoolType];

Exemple sur une méthode générique:

declare function doStuff<V extends MyCoolType[keyof MyCoolType]>(propertyName: keyof MyCoolType, value: V) => void;
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.