Pesquisa de site

Criando um tipo Typescript `Record<>` com enums numéricos


Em Typescript, é possível criar tipos de registro com enums de string:

enum AxisLabel { X = "X", Y = "Y" }
export const labelLookup: Record<AxisLabel, string> = {
  [AxisLabel.X]: "X axis",
  [AxisLabel.Y]: "Y Axis"
};

Eu preciso criar um objeto Record semelhante ao acima, mas não desejo usar um enum de string.

Quando eu tento algo assim:

enum AxisLabel { X, Y }
export const labelLookup: Record<AxisLabel, string> = {
  [AxisLabel.X]: "X axis",
  [AxisLabel.Y]: "Y Axis"
};

O Typescript produz o seguinte erro:

Type 'AxisLabel' does not satisfy the constraint 'string'.

É possível criar objetos JS com números e strings como nomes de seus membros.

Desejo fazer o mesmo em Typescript, mas sem recorrer a coerção insegura ou casts de tipo. Como posso criar tipos de enum numérico Record<> em Typescript sem usar enums de string, any ou casts de tipo?

Resposta #1

ATUALIZAÇÃO: O TypeScript 2.9 adicionou suporte para number e symbol como tipos de chave válidos, portanto o código acima não gera mais um erro, e esta resposta não é mais necessária, contanto que você esteja usando o TypeScript 2.9 ou superior.

Para TypeScript 2.8 e versões anteriores:

As chaves de objetos em JavaScript são, acredite ou não, sempre strings (ok, ou Symbols), mesmo para arrays. Quando você passa um valor que não é string como chave, ele é convertido em string primeiro. Mas, claro, as pessoas esperam que chaves numéricas façam sentido, especialmente para arrays. O TypeScript meio que reflete essa filosofia inconsistente: geralmente você só pode especificar chaves com valores de string (como em tipos mapeados como Record<K,V>). Quando essas situações interagem, surgem estranhezas.

Aqui está uma coisa que às vezes fiz: representar explicitamente a coerção de número para string com o seguinte tipo de tupla:

export type NumericStrings = ["0","1","2","3","4","5","6","7","8","9","10"] // etc

Observe que você pode estender isso pelo tempo que precisar. Então, você pode usar tipos de pesquisa para converter um tipo numérico em seu equivalente em string para uso em tipos mapeados. Assim:

export enum AxisLabel { X, Y }

// note the key is NumericStrings[AxisLabel], not AxisLabel    
export const labelLookup: Record<NumericStrings[AxisLabel], string> = {
  [AxisLabel.X]: "X axis",
  [AxisLabel.Y]: "Y Axis"
};

Isso funciona sem erro. Se você inspecionar o tipo de labelLookup, ele aparece como Record<"0" | "1", string>. Quando você tenta indexar em labelLookup, as seguintes coisas, em sua maioria esperadas, acontecem:

labelLookup[AxisLabel.X]; // okay
labelLookup[0]; // okay
labelLookup["0"]; // also okay

labelLookup[10]; // error
labelLookup.X; //error

Espero que isso ajude; boa sorte!

Resposta #2

A definição de record é muito específica, pois a chave deve ser atribuível a uma string, portanto não há como usar Record com um número; mais geralmente, a chave em um tipo mapeado deve ser uma string.

Você pode usar uma assinatura de índice regular, mas não pode restringir uma assinatura de índice a nada além de string ou número (de acordo com a especificação da linguagem), o que significa que qualquer número seria válido, não apenas os valores do enum:

export const labelLookup: { [index: number]: string } = {
    [AxisLabel.X]: "X axis",
    [AxisLabel.Y]: "Y Axis",
    [3] = "" // Also works but you don't want that
};

Artigos relacionados