Pesquisa de site

Como digitar um dicionário feito a partir de um objeto em TypeScript?


Muito frequentemente eu uso objetos como dicionários, por exemplo.

type Foo = { a: string }

type MyDictionary = { [key: string]: Foo }

function foo(dict: MyDictionary) {
  // Requirement 1: Should be of type Foo[]
  const values = Object.values(dict)

  // Requirement 2: Should be of type Foo | undefined
  const aValue = dict.b

  // Requirement 3: Should fail
  dict.c = undefined
}

Solução alternativa 1

Minha escolha padrão é

type MyDictionary = Record<string, Foo> // aka { [key: string]: Foo }

Isso irá passar no 1 e no 3, mas falhar no 2.

Solução alternativa 2

{ [key: string]: Foo | undefined }

Isso passará no 2, mas falhará no 1 e no 3.

Solução alternativa 3

Outra opção é manter a opção 1, mas usar uma função getter para deixar as coisas mais seguras

export function safeGet<T extends {}, K extends keyof T>(obj: T, key: K): T[K] | undefined {
  return obj[key]
}

safeGet(dict, 'b')

Alguma outra sugestão?

Nota: Eu sei que Map faz isso bem, mas usar Map nem sempre é um refatoramento prático. Esta pergunta é especificamente para objetos como dicionários.

Resposta #1

Você está procurando a funcionalidade fornecida pela opção de compilador --noUncheckedIndexedAccess, que adiciona undefined ao domínio do tipo que você obtém ao ler uma propriedade coberta por uma assinatura de índice, sem fazer o mesmo ao tipo que você tem permissão para escrever na mesma propriedade. E observe que isso não afeta o tipo de retorno de Object.values(), cuja declaração é

interface ObjectConstructor {
  values<T>(o: { [s: string]: T } | ArrayLike<T>): T[];
}

Observe que esta opção do compilador não faz parte do conjunto de opções do compilador --strict e, portanto, não é considerada uma opção de segurança de tipo "padrão". Isso porque, embora às vezes seja desejável se proteger contra undefined dessa maneira, pode ser bem irritante em outras circunstâncias, gerando erros em JavaScript idiomático muito comum.

Por exemplo, os tipos de array no TypeScript possuem uma assinatura de índice number. Habilitar --noUncheckedIndexedAccess atrapalha o caso de uso muito comum de iterar por um array percorrendo seus índices numéricos:

const arr = ["a", "b", "c", "d", "e"];
for (let i = 0; i < arr.length; i++) {
    console.log(arr[i].toUpperCase()); // error!
    // -------> ~~~~~~ Object is possibly undefined
}

Tecnicamente, arrays esparsos existem, então alguém poderia argumentar que esse erro deveria acontecer... mas na prática, muitas vezes é mais problemático do que vale a pena deixar o compilador fazer essas verificações.

Então, se você quiser esse verificador, precisará ativá-lo explicitamente.

Link do playground para o código

Resposta #2

A API que você está procurando é fornecida por Map.

type Foo = { a: string }

type MyDictionary = Map<string, Foo>();

function foo(dict: MyDictionary) {
  // Requirement 1: Should be of type Foo[]
  const values = [...dict.values()]

  // Requirement 2: Should be of type Foo | undefined
  const aValue = dict.get("b")

  // Requirement 3: Should fail
  dict.set("c", undefined)
}

Parque infantil

Artigos relacionados