Const type parameters e inferência mais precisa
1. O problema da inferência ampla em literais
1.1. Como o TypeScript infere tipos literais por padrão
Quando você escreve um literal em TypeScript, o compilador tende a inferir o tipo mais amplo possível. Por exemplo:
const mensagem = "hello"; // Tipo: "hello" (literal)
let saudacao = "hello"; // Tipo: string (amplo)
Essa diferença entre const e let é conhecida, mas em funções genéricas o problema se agrava:
function identidade<T>(arg: T): T {
return arg;
}
const resultado = identidade("hello"); // Tipo: string, não "hello"
1.2. A limitação de as const em variáveis e objetos
O as const ajuda, mas tem limitações:
const config = {
url: "https://api.exemplo.com",
timeout: 5000
} as const;
// Tipo: { readonly url: "https://api.exemplo.com"; readonly timeout: 5000 }
Funciona para objetos fixos, mas não resolve o problema em parâmetros de função.
1.3. Cenários reais onde a inferência ampla causa perda de precisão
function criarRota<TPath extends string>(path: TPath) {
return { path, metodo: "GET" as const };
}
const rota = criarRota("/usuarios/:id");
// TPath é inferido como string, não como "/usuarios/:id"
Isso impede pattern matching preciso e validações em tempo de compilação.
2. Introdução ao const type parameters
2.1. Sintaxe básica
function criarRota<const TPath extends string>(path: TPath) {
return { path, metodo: "GET" as const };
}
const rota = criarRota("/usuarios/:id");
// TPath agora é inferido como "/usuarios/:id"
2.2. Diferença entre type parameters comuns e const
// Sem const
function semConst<T extends string>(arg: T) { return arg; }
const a = semConst("teste"); // Tipo: string
// Com const
function comConst<const T extends string>(arg: T) { return arg; }
const b = comConst("teste"); // Tipo: "teste"
2.3. Comportamento para diferentes tipos literais
function capturar<const T>(valor: T): T {
return valor;
}
const num = capturar(42); // Tipo: 42
const bool = capturar(true); // Tipo: true
const str = capturar("abc"); // Tipo: "abc"
3. Inferência precisa em tuplas e arrays
3.1. Preservando comprimento exato de tuplas
function criarPar<const T extends readonly any[]>(...args: T): T {
return args;
}
const par = criarPar("a", 42, true);
// Tipo: readonly ["a", 42, true] - comprimento exato preservado
3.2. Inferência em arrays heterogêneos
function processarElementos<const T extends readonly any[]>(elementos: T) {
return elementos.map(el => String(el));
}
const resultado = processarElementos([1, "texto", false]);
// T é inferido como readonly [1, "texto", false]
3.3. Comparação com readonly arrays
// Sem const
function semConstArray<T extends readonly any[]>(arr: T) { return arr; }
const arr1 = semConstArray([1, 2]); // Tipo: readonly number[]
// Com const
function comConstArray<const T extends readonly any[]>(arr: T) { return arr; }
const arr2 = comConstArray([1, 2]); // Tipo: readonly [1, 2]
4. Objetos aninhados e deep inference
4.1. Comportamento em objetos com múltiplos níveis
function configurar<const T extends Record<string, any>>(config: T): T {
return config;
}
const appConfig = configurar({
api: { url: "https://api.com", port: 443 },
debug: true
});
// Tipo preserva estrutura aninhada: { api: { url: "https://api.com", port: 443 }, debug: true }
4.2. Limitações em inferência profunda
function processarDeep<const T>(data: T): T {
return data;
}
// Funciona para objetos simples
const obj1 = processarDeep({ a: { b: 1 } });
// Tipo: { a: { b: 1 } }
// Mas não para estruturas recursivas complexas
const obj2 = processarDeep({ items: [{ id: 1 }, { id: 2 }] });
// items é inferido como any[] em alguns casos
4.3. Combinação com satisfies
type Config = { url: string; timeout: number };
function criarConfig<const T extends Config>(config: T satisfies Config): T {
return config;
}
const config = criarConfig({ url: "https://api.com", timeout: 5000 });
// Tipo exato preservado, mas valida contra Config
5. Uso prático em APIs e bibliotecas
5.1. Tipando funções de configuração
interface OpcaoConector {
url: string;
headers?: Record<string, string>;
}
function criarConector<const T extends OpcaoConector>(opcoes: T) {
return {
conectar: () => console.log(`Conectando a ${opcoes.url}`),
opcoes
};
}
const conector = criarConector({
url: "https://api.exemplo.com",
headers: { Authorization: "Bearer token123" }
});
// headers preserva tipo literal
5.2. Criando builders type-safe
class QueryBuilder<T extends Record<string, any>> {
constructor(private query: T) {}
where<K extends keyof T>(key: K, value: T[K]) {
return new QueryBuilder({ ...this.query, [key]: value });
}
build(): T {
return this.query;
}
}
function criarQuery<const T extends Record<string, any>>(initial: T) {
return new QueryBuilder(initial);
}
const query = criarQuery({ status: "ativo" })
.where("status", "inativo")
.build();
// Tipo: { status: "ativo" } & { status: "inativo" }
5.3. Roteamento tipado com paths literais
type Rota = {
path: string;
params: Record<string, string>;
};
function definirRota<const TPath extends string>(path: TPath): {
path: TPath;
params: TPath extends `${string}:${infer P}/${infer Rest}`
? { [K in P | keyof definirRota<Rest>['params']]: string }
: TPath extends `${string}:${infer P}`
? { [K in P]: string }
: {};
} {
return { path, params: {} as any };
}
const rota = definirRota("/usuarios/:id/pedidos/:pedidoId");
// Tipo preserva parâmetros da rota
6. Interação com outras features do TypeScript
6.1. const vs as const
// Use const type parameter quando o valor vem como argumento
function comConst<const T>(arg: T): T { return arg; }
// Use as const para valores fixos
const valores = [1, 2, 3] as const;
6.2. Compatibilidade com tipos genéricos complexos
type Resposta<T> = { dados: T; erro?: string };
function processarResposta<const T>(dados: T): Resposta<T> {
return { dados };
}
const resposta = processarResposta({ usuario: "João", idade: 30 });
// Resposta<{ usuario: string; idade: number }>
6.3. Limitações conhecidas (TypeScript 5.0+)
// Não funciona com tipos union complexos
function limitado<const T extends string | number>(arg: T) {
return arg;
}
const x = limitado(Math.random() > 0.5 ? "texto" : 42);
// T é inferido como string | number, não como "texto" | 42
7. Padrões avançados e boas práticas
7.1. Uso em funções de alta ordem
function criarMiddleware<const T extends Record<string, any>>(handler: (ctx: T) => void) {
return (ctx: T) => {
console.log("Middleware executado");
handler(ctx);
};
}
const middleware = criarMiddleware((ctx: { usuario: string }) => {
console.log(ctx.usuario);
});
// T preserva tipo do contexto
7.2. Inferência condicional combinada
type ExtrairTipo<T> = T extends Array<infer U> ? U : T;
function processar<const T>(valor: T): ExtrairTipo<T> {
return (Array.isArray(valor) ? valor[0] : valor) as any;
}
const resultado = processar([1, 2, 3]); // Tipo: 1
7.3. Anti-patterns: quando evitar
// Evite usar const com tipos muito genéricos sem restrição
function ruim<const T>(arg: T): T { return arg; } // Muito permissivo
// Prefira restrições específicas
function bom<const T extends string | number>(arg: T): T { return arg; }
8. Comparação com alternativas e futuro
8.1. const vs type guards manuais vs satisfies
// Type guard manual
function isStringLiteral(valor: string): valor is "hello" | "world" {
return valor === "hello" || valor === "world";
}
// satisfies para validação
const config = { url: "https://api.com" } satisfies { url: string };
// const type parameter para inferência em funções
function melhor<const T extends string>(arg: T): T { return arg; }
8.2. Impacto no roadmap do TypeScript
O const type parameters é um passo importante para:
- Pattern matching mais preciso
- Validação de tipos em tempo de compilação
- Melhor integração com bibliotecas de validação
8.3. Possíveis evoluções
// Preview conceitual de futuras features
type Pattern<T> = T extends `${infer A}:${infer B}` ? [A, B] : never;
function match<const T extends string>(pattern: T): Pattern<T> {
return pattern.split(":") as any;
}
const [a, b] = match("nome:valor"); // Futuro: ["nome", "valor"]
Referências
- Documentação Oficial do TypeScript - Const type parameters — Notas de lançamento do TypeScript 5.0 com explicação detalhada sobre const type parameters
- TypeScript Deep Dive - Const Type Parameters — Guia prático com exemplos avançados de uso
- Artigo no Dev.to - TypeScript 5.0: Const Type Parameters Explained — Tutorial completo com casos de uso reais
- TypeScript Playground - Exemplos Interativos — Exemplos interativos oficiais para testar const type parameters
- Blog do Matt Pocock - Const Type Parameters — Artigo técnico com padrões avançados e boas práticas
- Stack Overflow - TypeScript const type parameters discussion — Discussão da comunidade com exemplos e esclarecimentos
- TypeScript GitHub - Pull Request de Implementação — Pull Request original que implementou const type parameters no compilador