Tipos de template literal
Um template literal em JavaScript monta uma string interpolando valores entre crases: `Olá, ${nome}`. O TypeScript leva a mesma sintaxe para o nível de tipos. Um tipo de template literal descreve o formato de uma string, com partes fixas e partes variáveis, e só aceita strings que se encaixam nesse formato:
type Saudacao = `Olá, ${string}`;
const a: Saudacao = "Olá, mundo"; // ok, casa com o formatoconst b: Saudacao = "Oi"; // erro: não começa com "Olá, "O ${string} é um buraco que qualquer string preenche, então Saudacao é o tipo de todas as strings que começam com "Olá, ". Você pode interpolar outros tipos primitivos também, como ${number} para o trecho numérico de uma string, e é isso que transforma strings, antes tipos opacos, em algo que o compilador consegue verificar por forma.
Interpolar uniões expande em combinações
Section titled “Interpolar uniões expande em combinações”O comportamento que dá poder de verdade aos template literals aparece quando você interpola uma união. Em vez de um buraco livre, cada união vira um conjunto de opções, e o tipo resultante é o produto de todas as combinações possíveis:
type Vertical = "top" | "bottom";type Horizontal = "left" | "right";
type Posicao = `${Vertical}-${Horizontal}`;// ^? "top-left" | "top-right" | "bottom-left" | "bottom-right"Duas uniões de dois membros geraram uma união de quatro strings, todas verificadas. Isso é útil sempre que existe um vocabulário fechado de strings que segue um padrão: variantes de um componente, nomes de variáveis CSS, chaves de tradução. Em vez de listar as quatro à mão e arriscar esquecer uma, você descreve a regra e deixa o compilador enumerar.
Os tipos intrínsecos de string
Section titled “Os tipos intrínsecos de string”O TypeScript embute quatro tipos que transformam o caixa de uma string no nível de tipo, e que combinam com template literals: Uppercase, Lowercase, Capitalize e Uncapitalize. Eles fazem o esperado:
type Grito = Uppercase<"socorro">;// ^? "SOCORRO"type Titulo = Capitalize<"nome do campo">;// ^? "Nome do campo"São chamados de intrínsecos porque não são escritos em TypeScript; o compilador os implementa por dentro. O uso mais comum deles é ajustar uma chave ao montar um nome derivado, como o get${Capitalize<...>} que apareceu nos tipos mapeados.
Desmontar strings com infer
Section titled “Desmontar strings com infer”Assim como o infer extraía o elemento de um array ou a cauda de uma tupla, ele extrai pedaços de uma string quando usado dentro de um padrão de template literal. Você descreve o formato, marca com infer a parte que quer capturar, e o compilador a devolve:
// Captura tudo que vem depois de "user_".type Sufixo<T extends string> = T extends `user_${infer Id}` ? Id : never;
type Id = Sufixo<"user_42">;// ^? "42"Combine isso com o que os capítulos anteriores mostraram e os usos práticos aparecem. Tipar nomes de eventos derivados de um estado, por exemplo, junta template literal, keyof e interpolação de união num único tipo:
type Estado = { nome: string; idade: number };
// Um evento "mudou" para cada campo do estado.type EventosDeMudanca = `${string & keyof Estado}Changed`;// ^? "nomeChanged" | "idadeChanged"Esse padrão é a espinha do event emitter tipado que o último capítulo monta: os nomes de evento válidos deixam de ser strings quaisquer e passam a ser derivados, um a um, das chaves do estado, de modo que ouvir um evento que não existe vira erro de compilação.
O exemplo que junta tudo: parâmetros de rota
Section titled “O exemplo que junta tudo: parâmetros de rota”O ponto alto dos template literals é extrair informação estruturada de uma string, e o caso didático perfeito é pegar os parâmetros de um padrão de rota, aquelas partes marcadas com dois-pontos. Ele combina três coisas que o curso já cobriu: casamento de template com infer, um condicional para ramificar, e recursão para varrer a string até o fim.
type ParamsDaRota<T extends string> = // Caso 1: há um ":param" seguido de mais caminho. Captura o param e recorre no resto. T extends `${string}:${infer Param}/${infer Resto}` ? Param | ParamsDaRota<Resto> // Caso 2: há um ":param" no fim, sem mais caminho. Captura e para. : T extends `${string}:${infer Param}` ? Param // Caso 3: não há mais nenhum ":param". Fim da recursão. : never;
type Params = ParamsDaRota<"/users/:userId/posts/:postId">;// ^? "userId" | "postId"Vale seguir a recursão uma volta. Na primeira passada, o caso 1 casa: ${string} engole "/users/", o : marca o início do parâmetro, Param captura "userId" até a barra seguinte, e Resto fica com "posts/:postId". O resultado é "userId" unido ao que a chamada recursiva sobre "posts/:postId" devolver. Nessa segunda passada não há barra depois do parâmetro, então o caso 1 falha e o caso 2 assume, capturando "postId" e encerrando. As duas capturas se somam em "userId" | "postId".
Um tipo desses deixa de ser curiosidade quando você o pluga num roteador: a função que registra "/users/:userId" pode exigir um handler cujo objeto de parâmetros tenha exatamente a chave userId, tipada, e um typo em userIdd vira erro no editor. É o mesmo princípio dos capítulos anteriores levado às strings: transformar uma convenção que se verificava com testes (ou não se verificava) numa garantia que o compilador dá de graça.
Com condicionais, tipos mapeados e agora template literals, você tem as três primitivas de transformação do sistema de tipos. O próximo capítulo cobra o débito que os últimos três acumularam: usar essas primitivas para reconstruir, do zero, os utility types que a biblioteca padrão oferece pronta.
Palavras-chave
Section titled “Palavras-chave”Conceitos: tipo de template literal, expansão combinatória de união, tipo intrínseco de string, extração por infer Intrínsecos: Uppercase, Lowercase, Capitalize, Uncapitalize Exemplos: Saudacao, Posicao, Sufixo, EventosDeMudanca, ParamsDaRota