Template literal types
Template literal types são uma feature poderosa do TypeScript introduzida na versão 4.1 que permite construir tipos baseados em strings dinâmicas usando a mesma sintaxe de template strings do JavaScript, mas operando no nível de tipos. Diferentemente das template strings do JavaScript, que produzem valores em tempo de execução, os template literal types geram novos tipos em tempo de compilação.
Sintaxe básica
A sintaxe utiliza crases e placeholders ${} com tipos:
type UserId = `user_${number}`;
// Tipo resultante: `user_${number}`
const validId: UserId = "user_123"; // ✅ OK
const invalidId: UserId = "admin_456"; // ❌ Erro: Tipo incompatível
Combinando Template Literals com Union Types
Quando interpolamos union types, o TypeScript expande automaticamente todas as combinações possíveis:
type EventName = `${'click' | 'hover' | 'focus'}_${'start' | 'end'}`;
// Resultado: "click_start" | "click_end" | "hover_start" | "hover_end" | "focus_start" | "focus_end"
type ApiEndpoint = `/api/${'users' | 'posts'}/${'list' | 'create' | 'delete'}`;
// Resultado: "/api/users/list" | "/api/users/create" | "/api/users/delete" | "/api/posts/list" | ...
function handleEvent(event: EventName) {
console.log(`Processing event: ${event}`);
}
handleEvent("click_start"); // ✅ OK
handleEvent("hover_end"); // ✅ OK
handleEvent("scroll_start"); // ❌ Erro
Manipulação de Strings com Tipos Utilitários Intrínsecos
O TypeScript fornece quatro tipos utilitários para manipular strings em tempo de compilação:
type CssProperty = 'background-color' | 'font-size' | 'margin-top';
// Convertendo para camelCase
type CamelCase<T extends string> = T extends `${infer First}_${infer Rest}`
? `${Lowercase<First>}${Capitalize<Rest>}`
: Lowercase<T>;
type CssCamelCase = CamelCase<CssProperty>;
// Resultado: "backgroundColor" | "fontSize" | "marginTop"
// Uppercase e Lowercase
type HttpMethod = 'GET' | 'POST' | 'PUT';
type HttpHeader = `X-${Capitalize<Lowercase<HttpMethod>>}-Token`;
// Resultado: "X-Get-Token" | "X-Post-Token" | "X-Put-Token"
Inferência e Extração de Partes com infer
O operador infer dentro de template literals permite extrair partes específicas de strings:
type ExtractId<T extends string> =
T extends `${infer Prefix}_${infer Id}_${infer Suffix}`
? { prefix: Prefix; id: Id; suffix: Suffix }
: never;
type Parsed = ExtractId<"user_123_active">;
// Resultado: { prefix: "user"; id: "123"; suffix: "active" }
type ExtractRouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<Rest>
: T extends `${string}:${infer Param}`
? Param
: never;
type RouteParams = ExtractRouteParams<"/users/:userId/posts/:postId">;
// Resultado: "userId" | "postId"
Validação e Parsing de Strings em Tempo de Compilação
Podemos criar tipos que validam formatos específicos combinando template literals com conditional types:
// Validação de UUID
type UUID = `${string}-${string}-${string}-${string}-${string}`;
type ValidateUUID<T extends string> =
T extends UUID ? T : never;
function processUUID<T extends string>(uuid: ValidateUUID<T>) {
return uuid;
}
processUUID("550e8400-e29b-41d4-a716-446655440000"); // ✅ OK
processUUID("invalid-uuid"); // ❌ Erro
// Validação de caminhos com parâmetros numéricos
type ValidPath<T extends string> =
T extends `/users/${number}/posts/${number}`
? T
: `Invalid path: ${T}`;
type Path1 = ValidPath<"/users/123/posts/456">; // ✅ "/users/123/posts/456"
type Path2 = ValidPath<"/users/abc/posts/456">; // ❌ "Invalid path: /users/abc/posts/456"
Padrões Avançados com Recursão em Template Literals
Tipos recursivos permitem manipular strings de tamanho variável:
// Join: concatena elementos de um tuple com um separador
type Join<T extends string[], Separator extends string = ""> =
T extends [infer First extends string, ...infer Rest extends string[]]
? Rest extends []
? First
: `${First}${Separator}${Join<Rest, Separator>}`
: "";
type PathJoined = Join<["users", "posts", "comments"], "/">;
// Resultado: "users/posts/comments"
// Split: divide uma string em um tuple
type Split<T extends string, Separator extends string> =
T extends `${infer First}${Separator}${infer Rest}`
? [First, ...Split<Rest, Separator>]
: [T];
type PathSplit = Split<"a.b.c.d", ".">;
// Resultado: ["a", "b", "c", "d"]
Limitações: O TypeScript limita a profundidade de recursão a aproximadamente 50 níveis. Para strings muito longas, considere alternativas.
Aplicações Práticas em Projetos Reais
Tipagem de rotas de frameworks
// React Router com tipos seguros
type RouteDefinition = {
path: string;
component: React.ComponentType;
};
type ExtractParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractParams<Rest>]: string }
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
type UserRoute = ExtractParams<"/users/:userId/posts/:postId">;
// Resultado: { userId: string; postId: string }
function navigate<T extends string>(path: T, params: ExtractParams<T>) {
// Implementação
}
navigate("/users/:userId/posts/:postId", { userId: "123", postId: "456" }); // ✅ OK
Geração de tipos para queries de banco de dados
type TableName = "users" | "posts" | "comments";
type ColumnName<T extends TableName> =
T extends "users" ? "id" | "name" | "email"
: T extends "posts" ? "id" | "title" | "content" | "authorId"
: "id" | "text" | "postId";
type Query<T extends TableName> =
`SELECT ${ColumnName<T>} FROM ${T} WHERE id = :id`;
type UserQuery = Query<"users">;
// Resultado: "SELECT id | name | email FROM users WHERE id = :id"
Armadilhas Comuns e Dicas de Performance
Limitação de profundidade
// ❌ Isso pode falhar para strings muito longas
type DeepRecursive<T extends string> =
T extends `${infer First}${infer Rest}`
? `${First}${DeepRecursive<Rest>}`
: "";
// ✅ Prefira soluções não recursivas quando possível
type SimpleValidation<T extends string> =
T extends `/api/${string}/v${number}` ? T : never;
Explosão combinatória
// ❌ Evite unions grandes em template literals
type Dangerous = `${1 | 2 | 3 | 4 | 5}_${'a' | 'b' | 'c' | 'd' | 'e'}_${'x' | 'y' | 'z'}`;
// 5 * 5 * 3 = 75 combinações (aceitável)
// Mas 10 * 10 * 10 = 1000 combinações já pode afetar performance
// ✅ Prefira tipos mais específicos
type SmallUnion = `${'read' | 'write'}_${'file' | 'db'}`;
// Apenas 4 combinações
Quando não usar template literals
// ❌ Use enum ou union simples para valores fixos
type Status = "active" | "inactive" | "pending"; // ✅ Melhor que template literal
// ❌ Evite para validações complexas em runtime
// Template literals são para tipos, não para lógica de negócio complexa
Dicas para depuração
// Use // @ts-expect-error para testar tipos
type TestType = `${'a' | 'b'}_${1 | 2}`;
// @ts-expect-error: "a_3" não está no tipo
const test: TestType = "a_3";
// Use o playground do TypeScript para visualizar tipos expandidos
// https://www.typescriptlang.org/play
Template literal types são uma ferramenta extremamente poderosa para criar APIs tipadas com segurança, validar formatos de strings em tempo de compilação e reduzir erros em tempo de execução. Use-os com moderação e sempre considere o impacto na performance e legibilidade do código.
Referências
- TypeScript 4.1 Release Notes - Template Literal Types — Documentação oficial da Microsoft sobre a introdução dos template literal types
- Template Literal Types - TypeScript Handbook — Guia completo no handbook oficial do TypeScript
- TypeScript Template Literal Types: Practical Use Cases — Tutorial prático de Matt Pocock com exemplos do mundo real
- Exploring Template Literal Types in TypeScript 4.1 — Artigo técnico detalhado no dev.to com exemplos avançados
- TypeScript Playground - Template Literal Types Examples — Ambiente interativo oficial para testar e visualizar template literal types
- Advanced TypeScript: Template Literal Types — Curso da Udemy cobrindo padrões avançados com template literals
- TypeScript Deep Dive: Template Literal Types — Capítulo do livro gratuito "TypeScript Deep Dive" de Basarat Ali Syed