Por que este tópico explode no Stack Overflow?
\n\nSe 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\nImagine: 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\nNeste 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.
Problema: objeto com chaves dinâmicas
\n\nSuponha 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\ninterface 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\nParece que funciona. Mas há um detalhe: Index Signature permite quaisquer chaves string. E se quisermos restringir o conjunto de chaves permitidas?
Abordagem 1: Index Signature — o clássico
\n\nIndex Signature é a maneira mais simples de definir o tipo para todos os valores de um objeto. Sintaxe:
\n\ninterface StringMap {\n [key: string]: string;\n}\n\nconst map: StringMap = {\n hello: 'world',\n foo: 'bar',\n};\n\n\nVantagens:
\n- \n
- Simplicidade e legibilidade \n
- Funciona com qualquer número de chaves \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
Erro comum de iniciantes: tentar adicionar um método a esse objeto:
\n\ninterface BadMap {\n [key: string]: string;\n length: number; // Erro! 'number' não é assignable a 'string'\n}\n\n\nIsso não funciona porque length viola o tipo geral. Se precisar misturar tipos, use union ou Mapped Types.
Abordagem 2: Record — helper nativo
\n\nTypeScript fornece o tipo nativo Record<K, V>, que faz o mesmo, mas com sintaxe mais concisa:
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\nQuando 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
Desvantagem: Record não permite definir tipos diferentes para chaves diferentes. Para isso, existem os Mapped Types.
Abordagem 3: Map — alternativa do ES6
\n\nSe você usa Map do JavaScript, o TypeScript o tipa muito bem