O typescript deriva o tipo de união de valores de tupla / array

99

Diga que tenho lista const list = ['a', 'b', 'c']

É possível derivar desse tipo de união de valor que é 'a' | 'b' | 'c'?

Eu quero isso porque quero definir o tipo que permite apenas valores de array estático e também precisa enumerar esses valores em tempo de execução, então eu uso array.

Exemplo de como pode ser implementado com um objeto indexado:

const indexed = {a: null, b: null, c: null}
const list = Object.keys(index)
type NeededUnionType = keyof typeof indexed

Gostaria de saber se é possível fazer isso sem usar um mapa indexado.

COR BRANCA
fonte
Então, essencialmente, você gostaria de gerar tipos instantaneamente?
SwiftsNamesake
Os tipos existem apenas durante a compilação, portanto, você não pode criá-los dinamicamente em tempo de execução.
jonrsharpe
Esta é uma pergunta interessante. Qual é o seu caso de uso?
unional

Respostas:

213

ATUALIZAÇÃO de fevereiro de 2019

No TypeScript 3.4, que deve ser lançado em março de 2019 , será possível dizer ao compilador para inferir o tipo de uma tupla de literais como uma tupla de literais , em vez de, digamos, string[]usando a as constsintaxe . Esse tipo de asserção faz com que o compilador deduza o tipo mais restrito possível para um valor, incluindo fazer tudo readonly. Deve ser assim:

const list = ['a', 'b', 'c'] as const; // TS3.4 syntax
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c';

Isso eliminará a necessidade de uma função auxiliar de qualquer tipo. Boa sorte novamente a todos!


ATUALIZAÇÃO julho de 2018

Parece que, a partir do TypeScript 3.0, será possível para o TypeScript inferir automaticamente os tipos de tupla . Depois de liberada, a tuple()função de que você precisa pode ser escrita sucintamente como:

export type Lit = string | number | boolean | undefined | null | void | {};
export const tuple = <T extends Lit[]>(...args: T) => args;

E então você pode usá-lo assim:

const list = tuple('a','b','c');  // type is ['a','b','c']
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c'

Espero que funcione para as pessoas!


ATUALIZAÇÃO dezembro de 2017

Desde que postei esta resposta, descobri uma maneira de inferir tipos de tupla se você estiver disposto a adicionar uma função à sua biblioteca. Verifique a função tuple()em tuple.ts . Usando-o, você pode escrever o seguinte e não se repetir:

const list = tuple('a','b','c');  // type is ['a','b','c']
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c'

Boa sorte!


ORIGINAL julho de 2017

Um problema é que o literal ['a','b','c']será inferido como tipo string[], portanto, o sistema de tipos esquecerá os valores específicos. Você pode forçar o sistema de tipos a lembrar cada valor como uma string literal:

const list = ['a' as 'a','b' as 'b','c' as 'c']; // infers as ('a'|'b'|'c')[]

Ou, talvez melhor, interprete a lista como um tipo de tupla:

const list: ['a','b','c'] = ['a','b','c']; // tuple

Essa é uma repetição irritante, mas pelo menos não apresenta um objeto estranho em tempo de execução.

Agora você pode obter o seu sindicato assim:

type NeededUnionType = typeof list[number];  // 'a'|'b'|'c'.

Espero que ajude.

Jcalz
fonte
Esta é uma excelente solução para um problema que encontro com frequência, quando preciso de verificações de tempo de execução (usando a tupla) e verificações de tempo de compilação usando uma união de tipo. Alguém sabe se há esforços para adicionar suporte para digitação de tupla implícita à linguagem?
Jørgen Tvedt
Tentei a sua solução, mas NÃO funciona comcont xs = ['a','b','c']; const list = tuple(...xs);
Miguel Carvajal
1
Não é suposto a trabalhar com isso, já que no momento em que você faz const xs = ['a','b','c']o compilador já aumentou xspara string[]e completamente esquecido os valores específicos. Não posso evitar esse comportamento, pelo menos a partir do TS3.2 (pode haver uma as constnotação futura que funcione). De qualquer forma, acho que a resposta como está ainda é a mais correta que posso fazer (menciono lá que ['a','b','c']está inferido como string[]), então não tenho certeza do que mais você precisa.
jcalz
3
alguém pode explicar o que [number]faz list[number]?
Orelus
3
É analisado como (typeof list)[number]... não typeof (list[number]). O tipo T[K]é um tipo de pesquisa que obtém o tipo da propriedade de Tcuja chave é K. Em (typeof list)[number], você está obtendo os tipos de propriedades de (typeof list)cujas chaves pertencem number. Arrays como typeof listtêm assinaturas de índice numérico , portanto, sua numberchave produz a união de todas as propriedades indexadas numericamente.
jcalz de
16

Atualização para TypeScript 3.4:

Uma nova sintaxe chamada "contextos const" que chegará no TypeScript 3.4 permitirá uma solução ainda mais simples que não requer uma chamada de função conforme demonstrado. Este recurso está atualmente em análise, conforme visto neste PR .

Resumindo, essa sintaxe permite a criação de matrizes imutáveis ​​que possuem um tipo restrito (ou seja, o tipo em ['a', 'b', 'c']vez de ('a' | 'b' | 'c')[]ou string[]). Dessa forma, podemos criar tipos de união a partir de literais tão fácil quanto mostrado abaixo:

const MY_VALUES = <const> ['a', 'b', 'c']
type MyType = typeof MY_VALUES[number]

Na sintaxe alternativa:

const MY_VALUES = ['a', 'b', 'c'] as const
type MyType = typeof MY_VALUES[number]
ggradnig
fonte
Olá, acabei de encontrar sua resposta após postar esta pergunta. Vou deixar você ganhar algum carma se quiser responder;) stackoverflow.com/questions/56113411/…
Sebastien Lorber
2

Não é possível fazer isso com Array.

O motivo é que, mesmo se você declarar a variável como const, o conteúdo de um array ainda pode mudar, portanto, @jonrsharpe menciona que isso é tempo de execução.

Dado o que você deseja, pode ser melhor usar interfacecom keyof:

interface X {
    a: string,
    b: string
}

type Y = keyof X  // Y: 'a' | 'b'

Ou enum:

enum X { a, b, c }
unional
fonte
Existe uma maneira de excluir certos campos? Por exemplo, se eu quiser apenas o acampo ..
neomib
Você pode usar Pick<T, U>para isso.
unional
0

Se estiver usando um objeto para armazenar "constantes", esta é uma maneira de implementar a mesma ideia:

(Observe o 'as const' para alterar o tipo de keyOne e keyTwo de string para literal.)

const configObj = {
  keyOne: 'literalTypeValueOne' as const,
  keyTwo: 'literalTypeValueTwo' as const,
};

const typeValues = [configObj.keyOne, configObj.keyTwo] as const;
type MyType = typeof typeValues[number];
Bjørnar Hvidsten
fonte