Existe um `valueof` semelhante a` keyof` no TypeScript?


119

Eu quero ser capaz de atribuir uma propriedade de objeto a um valor dado uma chave e um valor como entradas, mas ainda ser capaz de determinar o tipo do valor. É um pouco difícil de explicar, então este código deve revelar o 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

Tentei mudar value: anypara, value: valueof JWTmas não funcionou.

Idealmente, onChange('expire', 1337)falharia porque 1337não é um tipo de data.

Como posso mudar value: anypara o valor da chave fornecida?

Respostas:


220

ATUALIZAÇÃO: Parece que o título da pergunta atrai pessoas que procuram uma união de todos os tipos de valor de propriedade possíveis, de forma análoga à forma como keyoffornece a união de todos os tipos de chave de propriedade possíveis. Vamos ajudar essas pessoas primeiro. Você pode fazer um ValueOfanálogo a keyof, usando tipos de pesquisa com keyof Tcomo a chave, assim:

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

o que dá a você

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

Para a pergunta conforme declarada, você pode usar chaves individuais, mais restritas do que keyof T, para extrair apenas o tipo de valor de seu interesse:

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

Para garantir que o par chave / valor "corresponda" corretamente em uma função, você deve usar genéricos , bem como tipos de pesquisa, 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

A ideia é que o keyparâmetro permita ao compilador inferir o Kparâmetro genérico . Em seguida, ele requer que valuecorresponda JWT[K], o tipo de pesquisa de que você precisa.

Espero que ajude; boa sorte!


Eu quero criar um a tuplepartir de valores. Existe alguma chance de fazer isso?
— Morteza Tourani

Isso soa como uma pergunta não relacionada, então você provavelmente deve perguntar em outro lugar, com um exemplo reproduzível mínimo para que as pessoas tenham certeza de entender seu caso de uso. Boa sorte.
— jcalz

1
Tive um problema ao usar um enum com valor de string com membros de função. Para lidar bem com isso, você pode usar type StringValueOf<T> = T[keyof T] & string;. Os melhores documentos que encontrei em enums de string são as notas de versão
— karmakaze

41

Se alguém ainda procura a implementação de valueofpara qualquer propósito, este é o que eu criei:

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 esperado :) Retorna uma união de todos os tipos possíveis


2
Isso é legal. Quase resolve o meu outro problema , mas a opção não parece restringir o tipo.
— elegante

Eu quero criar um a tuplepartir de valores. Existe alguma chance de fazer isso?
— Morteza Tourani

12

Existe outra maneira de extrair o tipo de união do objeto:

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

Resultado: 1 | "some_string"


Isso consté muito valioso, o TypeScript fornecerá os próprios valores e removerá duplicatas; É muito bom para dicionários.
— Noam

Exatamente o que eu estava procurando !
— Mavrick RMX '21 de

2

Agradeço as respostas existentes que resolvem o problema perfeitamente. Só queria adicionar uma lib incluiu este tipo de utilitário, se você preferir importar este comum.

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

Experimente isto:

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

Ele funciona em uma matriz ou em um objeto simples.

// 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

One-liner:

type ValueTypesOfPropFromMyCoolType = MyCoolType[keyof MyCoolType];

Exemplo de método genérico:

declare function doStuff<V extends MyCoolType[keyof MyCoolType]>(propertyName: keyof MyCoolType, value: V) => void;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.