Template Literal Types em TypeScript
Template Literal Types permitem criar tipos baseados em literais de string usando a sintaxe de template literals. Introduzidos no TypeScript 4.4, eles são fundamentais para sistemas de tipos mais expressivos e seguros. Essencialmente, você define um tipo usando a sintaxe ${} dentro de backticks, possibilitando composição dinâmica de tipos.
O caso de uso mais comum é validar e criar tipos para strings com padrões específicos. Imagine um sistema de eventos onde você precisa garantir que event handlers sigam uma convenção de nomenclatura:
type EventType = "click" | "hover" | "focus";
type EventHandler = `on${Capitalize<EventType>}`;
// Resultado: "onClick" | "onHover" | "onFocus"
const handler: EventHandler = "onClick"; // ✓ válido
const invalid: EventHandler = "onchange"; // ✗ erro
Combinando Union Types
Template Literal Types brilham ao combinar múltiplas unions, criando todas as combinações possíveis:
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = "users" | "posts" | "comments";
type APIRoute = `/${HTTPMethod}/${Endpoint}`;
// Gera 12 combinações automaticamente
const route1: APIRoute = "/GET/users"; // ✓
const route2: APIRoute = "/POST/posts"; // ✓
const route3: APIRoute = "/PATCH/users"; // ✗ erro
Type Inference e Extração
Você pode extrair partes de uma string usando infer em tipos condicionais:
type ExtractMethod<T> =
T extends `/${infer M}/${infer E}` ? M : never;
type Method = ExtractMethod<"/GET/users">; // "GET"
Recursive Types: Estruturando Dados Infinitos
Recursive Types permitem que um tipo se referencie, essencial para estruturas de árvore, grafos ou padrões aninhados. Ao contrário da recursão infinita, o TypeScript "expande" o tipo apenas quando necessário, mantendo performance.
A aplicação clássica é criar tipos para estruturas aninhadas arbitrárias. Considere um sistema de validação de objetos profundamente aninhados:
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
interface User {
name: string;
profile: {
bio: string;
settings: {
notifications: boolean;
}
}
}
const partial: DeepPartial<User> = {
profile: {
settings: {} // válido: qualquer nível pode ser omitido
}
};
Recursive Types para Estruturas Heterogêneas
Sistemas que processam JSONs complexos se beneficiam enormemente. Veja uma implementação de tipo para qualquer JSON válido:
type JSON =
| string
| number
| boolean
| null
| JSON[]
| { [key: string]: JSON };
const validJSON: JSON = {
user: {
name: "Ana",
scores: [10, 20, { nested: true }],
active: null
}
}; // ✓ totalmente type-safe
Limiting Recursion com Profundidade Máxima
Para evitar problemas de performance em certos cenários, você pode limitar a profundidade de recursão:
type DeepReadonly<T, Depth extends number = 5> =
Depth extends 0
? T
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K], [-1, 0, 1, 2, 3, 4][Depth]> }
: T;
Combinando Template Literals com Tipos Recursivos
A fusão de ambas as técnicas cria sistemas de tipos incrivelmente poderosos. Um exemplo prático: validar objetos com chaves dinâmicas baseadas em um padrão:
type DeepKeys<T, Prefix = ""> = T extends object
? {
[K in keyof T]: K extends string
? `${Prefix}${K}` | DeepKeys<T[K], `${Prefix}${K}.`>
: never
}[keyof T]
: never;
interface Config {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
}
}
cache: {
enabled: boolean;
}
}
type ConfigPaths = DeepKeys<Config>;
// "database" | "database.host" | "database.port" |
// "database.credentials" | "database.credentials.username" | ...
function getConfig(key: ConfigPaths): string | number | boolean {
// implementação segura
return "";
}
getConfig("database.credentials.username"); // ✓
getConfig("database.invalid"); // ✗ erro em tempo de compilação
Esse padrão é usado em bibliotecas como Zod, tRPC e frameworks modernos para garantir type-safety em configurações complexas. A recursão navega por cada nível do objeto, enquanto template literals constroem strings do caminho dinamicamente.
Conclusão
Template Literal Types e Recursive Types são ferramentas avançadas que transformam o TypeScript de uma camada de validação em um poderoso sistema de constraints em tempo de compilação. Template Literals permitem criar tipos baseados em padrões de string com composição elegante, enquanto Recursive Types resolvem o desafio de representar estruturas aninhadas de profundidade arbitrária.
Dominar essas técnicas eleva significativamente a qualidade do seu código: erros são capturados antes de executar, autocompletar fica mais inteligente, e refatorações ficam seguras. Comece praticando com casos simples (validação de rotas, chaves de configuração) e evolua gradualmente para sistemas mais complexos.