Introdução: Por que o TypeScript reclama do seu código?
\n\nVocê já escreveu código em TypeScript que parece absolutamente lógico, mas o compilador de repente exibe um erro assustador? Se você já se deparou com a mensagem «An index signature parameter type cannot be a union type. Consider using a mapped object type instead» — você não está sozinho. Esse erro é uma das perguntas mais populares no Stack Overflow (milhares de visualizações!) e muitas vezes confunde até mesmo desenvolvedores experientes.
\n\nNeste artigo, não vamos apenas mostrar como corrigir o erro. Vamos entender por que o TypeScript é tão rigoroso e como usar mapped object types para tornar seu código não apenas funcional, mas também mais seguro. Vamos lá!
\n\nO que é index signature e para que serve?
\n\nImagine que você está criando um objeto cujas chaves são strings e os valores são números. Por exemplo, um dicionário com preços de produtos:
\n\n// Objeto comum\nconst prices = {\n apple: 100,\n banana: 50,\n cherry: 200\n};\n\nMas e se você quiser dizer ao TypeScript: «Olha, este objeto pode ter quaisquer chaves de string, e todos os valores serão números»? Para isso, existe o index signature — uma sintaxe especial que descreve um padrão para todas as propriedades do objeto:
\n\n// Index signature: [key: string]: number\ninterface PriceMap {\n [key: string]: number;\n}\n\nconst prices: PriceMap = {\n apple: 100,\n banana: 50\n};\n\nAqui, [key: string]: number é o index signature. Ele diz: «Chaves são strings, valores são números». Simples, certo?
E qual é o problema com tipos union?
\n\nAgora imagine que você quer limitar as chaves possíveis a apenas duas opções: 'name' ou 'age'. Parece lógico escrever assim:
// ❌ ERRO! Não pode\ninterface Person {\n [key: 'name' | 'age']: string | number;\n}\n\nE então o TypeScript exibe aquele erro: «An index signature parameter type cannot be a union type. Consider using a mapped object type instead».
\n\nPor quê? Porque o index signature foi feito para descrever um conjunto ilimitado de chaves. Se você quer definir uma lista específica de chaves, isso não é mais um padrão, mas uma descrição exata da estrutura. O TypeScript não permite misturar esses conceitos.
\n\nComo corrigir o erro? Usando Mapped Object Type
\n\nA solução que o próprio compilador sugere é usar mapped object type (tipo de objeto mapeado). Parece complicado, mas na prática é apenas uma forma de «percorrer» todas as chaves de um tipo union e criar uma propriedade para cada uma.
\n\nVeja como fica:
\n\n// ✅ CORRETO: usando mapped type\ntype Person = {\n [K in 'name' | 'age']: K extends 'name' ? string : number;\n};\n\n// Ou uma versão mais simples, se os tipos de valores forem iguais:\ntype PersonSimple = {\n [K in 'name' | 'age']: string | number;\n};\n\nVamos analisar a sintaxe [K in 'name' | 'age']:
- \n
K— é uma variável que assume, um por vez, cada valor do tipo union ('name', depois'age'). \n in— palavra-chave que inicia um «loop» sobre os tipos. \n 'name' | 'age'— nosso conjunto de chaves. \n
Como resultado, o TypeScript criará um objeto com duas propriedades específicas: name e age. O erro desapareceu!
Exemplo com tipos de valores diferentes
\n\nMuitas vezes, chaves diferentes precisam ter tipos de valores diferentes. Com tipos condicionais (conditional types), isso é resolvido de forma elegante:
\n\ntype Config = {\n [K in 'host' | 'port' | 'debug']: \n K extends 'host' ? string :\n K extends 'port' ? number :\n boolean;\n};\n\n// Agora: