Como controlar o tipo de elementos em objeto indexado TypeScript

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que este tópico explode no Stack Overflow?

\n\n

Se você já escreveu em TypeScript e tentou criar um objeto onde todos os valores devem ser do mesmo tipo, certamente já enfrentou esse problema. A pergunta "Enforcing the type of the indexed members of a Typescript object" acumulou milhares de visualizações no Stack Overflow não por acaso — é um "clássico" para desenvolvedores que migram do JavaScript.

\n\n

Imagine: você tem um dicionário de cache, uma configuração com chaves dinâmicas ou apenas um mapa com números. Você quer que o TypeScript garanta que cada valor no objeto seja string, número ou algum tipo complexo. Mas o compilador, por algum motivo, deixa passar erros. Familiar?

\n\n

Neste artigo, vamos analisar todas as principais abordagens para resolver essa tarefa: desde simples Index Signatures até avançados Mapped Types. Você aprenderá quando usar cada método e quais armadilhas o aguardam.

\n\n

Problema: objeto com chaves dinâmicas

\n\n

Suponha que estamos escrevendo um sistema de gerenciamento de usuários. Temos um objeto onde as chaves são IDs de usuários e os valores são seus nomes:

\n\n
interface UserCache {\n  [userId: string]: string;\n}\n\nconst cache: UserCache = {\n  'user_1': 'Alice',\n  'user_2': 'Bob',\n  // 'user_3': 123; // Erro! Deve ser string\n};\n\ncache['user_4'] = 'Charlie'; // OK\ncache['user_5'] = 42; // Erro! Type 'number' não é assignable a 'string'\n
\n\n

Parece que funciona. Mas há um detalhe: Index Signature permite quaisquer chaves string. E se quisermos restringir o conjunto de chaves permitidas?

\n\n

Abordagem 1: Index Signature — o clássico

\n\n

Index Signature é a maneira mais simples de definir o tipo para todos os valores de um objeto. Sintaxe:

\n\n
interface StringMap {\n  [key: string]: string;\n}\n\nconst map: StringMap = {\n  hello: 'world',\n  foo: 'bar',\n};\n
\n\n

Vantagens:

\n
    \n
  • Simplicidade e legibilidade
  • \n
  • Funciona com qualquer número de chaves
  • \n
\n\n

Desvantagens:

\n
    \n
  • Não é possível restringir o conjunto de chaves
  • \n
  • Todos os valores devem ser do mesmo tipo (ou union)
  • \n
  • Não funciona com valores undefined (requer cuidado)
  • \n
\n\n

Erro comum de iniciantes: tentar adicionar um método a esse objeto:

\n\n
interface BadMap {\n  [key: string]: string;\n  length: number; // Erro! 'number' não é assignable a 'string'\n}\n
\n\n

Isso não funciona porque length viola o tipo geral. Se precisar misturar tipos, use union ou Mapped Types.

\n\n

Abordagem 2: Record — helper nativo

\n\n

TypeScript fornece o tipo nativo Record<K, V>, que faz o mesmo, mas com sintaxe mais concisa:

\n\n
type StringRecord = Record<string, string>;\n\nconst config: StringRecord = {\n  apiUrl: 'https://api.example.com',\n  timeout: '5000', // OK, string\n  // port: 8080, // Erro!\n};\n\n// É possível restringir chaves:\ntype AllowedKeys = 'name' | 'email' | 'phone';\ntype UserRecord = Record<AllowedKeys, string>;\n\nconst user: UserRecord = {\n  name: 'Alice',\n  email: 'alice@example.com',\n  phone: '+1234567890',\n  // age: '30', // Erro! 'age' não está em AllowedKeys\n};\n
\n\n

Quando usar:

\n
    \n
  • Quando precisar criar rapidamente um dicionário tipado
  • \n
  • Quando as chaves forem um conjunto específico de valores (union)
  • \n
  • Para configurações com campos conhecidos
  • \n
\n\n

Desvantagem: Record não permite definir tipos diferentes para chaves diferentes. Para isso, existem os Mapped Types.

\n\n

Abordagem 3: Map — alternativa do ES6

\n\n

Se você usa Map do JavaScript, o TypeScript o tipa muito bem

Blogs

Book Recommendations