Explore os poderosos tipos literais de template do TypeScript para manipulação avançada de strings, correspondência de padrões e validação. Aprenda com exemplos práticos e casos de uso reais.
Tipos Literais de Template: Correspondência de Padrões de String e Validação em TypeScript
O sistema de tipos do TypeScript está em constante evolução, oferecendo aos desenvolvedores ferramentas mais poderosas para expressar lógica complexa e garantir a segurança do tipo. Uma das características mais interessantes e versáteis introduzidas nas versões recentes são os tipos literais de template. Esses tipos permitem manipular strings no nível do tipo, permitindo a correspondência e validação avançada de padrões de string. Isso abre um novo mundo de possibilidades para criar aplicativos mais robustos e sustentáveis.
O que são Tipos Literais de Template?
Os tipos literais de template são uma forma de tipo construída combinando tipos literais de string e tipos de união, semelhantes a como os literais de template funcionam em JavaScript. No entanto, em vez de criar strings de tempo de execução, eles criam novos tipos com base nos existentes.
Aqui está um exemplo básico:
type Greeting<T extends string> = `Hello, ${T}!`;
type MyGreeting = Greeting<"World">; // type MyGreeting = "Hello, World!"
Neste exemplo, `Greeting` é um tipo literal de template que recebe um tipo de string `T` como entrada e retorna um novo tipo que é a concatenação de "Hello, ", `T` e "!".
Correspondência Básica de Padrões de String
Os tipos literais de template podem ser usados para executar a correspondência básica de padrões de string. Isso permite criar tipos que são válidos somente se corresponderem a um determinado padrão.
Por exemplo, você pode criar um tipo que aceita apenas strings que começam com "prefixo-":
type PrefixedString<T extends string> = T extends `prefix-${string}` ? T : never;
type ValidPrefixedString = PrefixedString<"prefix-valid">; // type ValidPrefixedString = "prefix-valid"
type InvalidPrefixedString = PrefixedString<"invalid">; // type InvalidPrefixedString = never
Neste exemplo, `PrefixedString` usa um tipo condicional para verificar se a string de entrada `T` começa com "prefixo-". Se isso acontecer, o tipo é `T` em si; caso contrário, é `never`. `never` é um tipo especial no TypeScript que representa o tipo de valores que nunca ocorrem, excluindo efetivamente a string inválida.
Extraindo Partes de uma String
Os tipos literais de template também podem ser usados para extrair partes de uma string. Isso é particularmente útil quando você precisa analisar dados de strings e convertê-los em tipos diferentes.
Digamos que você tenha uma string que representa uma coordenada no formato "x:10,y:20". Você pode usar tipos literais de template para extrair os valores x e y:
type CoordinateString = `x:${number},y:${number}`;
type ExtractX<T extends CoordinateString> = T extends `x:${infer X},y:${number}` ? X : never;
type ExtractY<T extends CoordinateString> = T extends `x:${number},y:${infer Y}` ? Y : never;
type XValue = ExtractX<"x:10,y:20">; // type XValue = 10
type YValue = ExtractY<"x:10,y:20">; // type YValue = 20
Neste exemplo, `ExtractX` e `ExtractY` usam a palavra-chave `infer` para capturar as partes da string que correspondem ao tipo `number`. `infer` permite extrair um tipo de uma correspondência de padrão. Os tipos capturados são então usados como o tipo de retorno do tipo condicional.
Validação Avançada de String
Os tipos literais de template podem ser combinados com outros recursos do TypeScript, como tipos de união e tipos condicionais, para realizar a validação avançada de string. Isso permite criar tipos que impõem regras complexas sobre a estrutura e o conteúdo das strings.
Por exemplo, você pode criar um tipo que valida strings de data ISO 8601:
type Year = `${number}${number}${number}${number}`;
type Month = `0${number}` | `10` | `11` | `12`;
type Day = `${0}${number}` | `${1 | 2}${number}` | `30` | `31`;
type ISODate = `${Year}-${Month}-${Day}`;
type ValidDate = ISODate extends "2023-10-27" ? true : false; // true
type InvalidDate = ISODate extends "2023-13-27" ? true : false; // false
function processDate(date: ISODate) {
// Function logic here. TypeScript enforces the ISODate format.
return `Processing date: ${date}`;
}
console.log(processDate("2024-01-15")); // Works
//console.log(processDate("2024-1-15")); // TypeScript error: Argument of type '"2024-1-15"' is not assignable to parameter of type '`${number}${number}${number}${number}-${0}${number}-${0}${number}` | `${number}${number}${number}${number}-${0}${number}-${1}${number}` | ... 14 more ... | `${number}${number}${number}${number}-12-31`'.
Aqui, `Year`, `Month` e `Day` são definidos usando tipos literais de template para representar os formatos válidos para cada parte da data. `ISODate` então combina esses tipos para criar um tipo que representa uma string de data ISO 8601 válida. O exemplo também demonstra como esse tipo pode ser usado para impor a formatação de dados em uma função, impedindo que formatos de data incorretos sejam transmitidos. Isso melhora a confiabilidade do código e evita erros de tempo de execução causados por entrada inválida.
Casos de Uso no Mundo Real
Os tipos literais de template podem ser usados em uma variedade de cenários do mundo real. Aqui estão alguns exemplos:
- Validação de Formulário: Você pode usar tipos literais de template para validar o formato de entradas de formulário, como endereços de e-mail, números de telefone e códigos postais.
- Validação de Solicitação de API: Você pode usar tipos literais de template para validar a estrutura dos payloads de solicitação de API, garantindo que eles estejam em conformidade com o formato esperado. Por exemplo, validar um código de moeda (por exemplo, "USD", "EUR", "GBP").
- Análise de Arquivo de Configuração: Você pode usar tipos literais de template para analisar arquivos de configuração e extrair valores com base em padrões específicos. Considere validar caminhos de arquivo em um objeto de configuração.
- Enums Baseados em String: Você pode criar enums baseados em string com validação usando tipos literais de template.
Exemplo: Validação de Códigos de Moeda
Vamos dar uma olhada em um exemplo mais detalhado de validação de códigos de moeda. Queremos garantir que apenas códigos de moeda ISO 4217 válidos sejam usados em nosso aplicativo. Esses códigos são normalmente três letras maiúsculas.
type CurrencyCode = `${Uppercase<string>}${Uppercase<string>}${Uppercase<string>}`;
function formatCurrency(amount: number, currency: CurrencyCode) {
// Function logic to format currency based on the provided code.
return `$${amount} ${currency}`;
}
console.log(formatCurrency(100, "USD")); // Works
//console.log(formatCurrency(100, "usd")); // TypeScript error: Argument of type '"usd"' is not assignable to parameter of type '`${Uppercase}${Uppercase}${Uppercase}`'.
//More precise example:
type ValidCurrencyCode = "USD" | "EUR" | "GBP" | "JPY" | "CAD" | "AUD"; // Extend as needed
type StronglyTypedCurrencyCode = ValidCurrencyCode;
function formatCurrencyStronglyTyped(amount: number, currency: StronglyTypedCurrencyCode) {
return `$${amount} ${currency}`;
}
console.log(formatCurrencyStronglyTyped(100, "EUR")); // Works
//console.log(formatCurrencyStronglyTyped(100, "CNY")); // TypeScript error: Argument of type '"CNY"' is not assignable to parameter of type '"USD" | "EUR" | "GBP" | "JPY" | "CAD" | "AUD"'.
Este exemplo demonstra como criar um tipo `CurrencyCode` que aceita apenas strings que consistem em três caracteres maiúsculos. O segundo exemplo, mais fortemente tipado, mostra como restringir isso ainda mais a uma lista predefinida de moedas aceitáveis.
Exemplo: Validação de Caminhos de Endpoint da API
Outro caso de uso é validar caminhos de endpoint da API. Você pode definir um tipo que representa uma estrutura de endpoint da API válida, garantindo que as solicitações sejam feitas aos caminhos corretos. Isso é especialmente útil em arquiteturas de microsserviços, onde vários serviços podem expor APIs diferentes.
type APIServiceName = "users" | "products" | "orders";
type APIEndpointPath = `/${APIServiceName}/${string}`;
function callAPI(path: APIEndpointPath) {
// API call logic
console.log(`Calling API: ${path}`);
}
callAPI("/users/123"); // Valid
callAPI("/products/details"); // Valid
//callAPI("/invalid/path"); // TypeScript error
// Even more specific:
type APIAction = "create" | "read" | "update" | "delete";
type APIEndpointPathSpecific = `/${APIServiceName}/${APIAction}`;
function callAPISpecific(path: APIEndpointPathSpecific) {
// API call logic
console.log(`Calling specific API: ${path}`);
}
callAPISpecific("/users/create"); // Valid
//callAPISpecific("/users/list"); // TypeScript error
Isso permite que você defina a estrutura dos endpoints da API com mais precisão, evitando erros de digitação e garantindo a consistência em todo o seu aplicativo. Este é um exemplo básico; padrões mais complexos podem ser criados para validar parâmetros de consulta e outras partes do URL.
Benefícios do Uso de Tipos Literais de Template
O uso de tipos literais de template para correspondência e validação de padrões de string oferece vários benefícios:
- Segurança de Tipo Aprimorada: Os tipos literais de template permitem impor restrições de tipo mais rígidas em strings, reduzindo o risco de erros de tempo de execução.
- Legibilidade de Código Aprimorada: Os tipos literais de template tornam seu código mais legível, expressando claramente o formato esperado das strings.
- Maior Manutenibilidade: Os tipos literais de template tornam seu código mais sustentável, fornecendo uma única fonte de verdade para regras de validação de string.
- Melhor Experiência do Desenvolvedor: Os tipos literais de template fornecem melhor preenchimento automático e mensagens de erro, melhorando a experiência geral do desenvolvedor.
Limitações
Embora os tipos literais de template sejam poderosos, eles também têm algumas limitações:
- Complexidade: Os tipos literais de template podem se tornar complexos, especialmente ao lidar com padrões intrincados. É crucial equilibrar os benefícios da segurança de tipo com a manutenção do código.
- Desempenho: Os tipos literais de template podem afetar o desempenho da compilação, especialmente em projetos grandes. Isso ocorre porque o TypeScript precisa executar uma verificação de tipo mais complexa.
- Suporte Limitado a Expressões Regulares: Embora os tipos literais de template permitam a correspondência de padrões, eles não oferecem suporte a toda a gama de recursos de expressão regular. Para validação de string altamente complexa, expressões regulares de tempo de execução ainda podem ser necessárias junto com esses construtos de tipo para uma higienização de entrada adequada.
Melhores Práticas
Aqui estão algumas práticas recomendadas para ter em mente ao usar tipos literais de template:
- Comece Simples: Comece com padrões simples e aumente gradualmente a complexidade conforme necessário.
- Use Nomes Descritivos: Use nomes descritivos para seus tipos literais de template para melhorar a legibilidade do código.
- Documente Seus Tipos: Documente seus tipos literais de template para explicar seu propósito e uso.
- Teste Exaustivamente: Teste seus tipos literais de template completamente para garantir que eles se comportem como esperado.
- Considere o Desempenho: Esteja atento ao impacto dos tipos literais de template no desempenho da compilação e otimize seu código de acordo.
Conclusão
Os tipos literais de template são um recurso poderoso no TypeScript que permite realizar manipulação, correspondência e validação avançadas de string no nível do tipo. Ao usar tipos literais de template, você pode criar aplicativos mais robustos, sustentáveis e com segurança de tipo. Embora tenham algumas limitações, os benefícios do uso de tipos literais de template geralmente superam as desvantagens, tornando-os uma ferramenta valiosa no arsenal de qualquer desenvolvedor TypeScript. À medida que a linguagem TypeScript continua a evoluir, entender e utilizar esses recursos de tipo avançados será crucial para construir software de alta qualidade. Lembre-se de equilibrar a complexidade com a legibilidade e sempre priorizar testes completos.