O que Todo Dev Deve Saber sobre Template Literal Types e Recursive Types em TypeScript Já leu

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: on${Capitalize } Combinando Union Types Template Literal Types brilham ao combinar múltiplas unions, criando todas as combinações possíveis: /${HTTPMethod}/${Endpoint} Type Inference e Extração Você pode extrair partes de uma string usando em tipos condicionais: /${infer M}/${infer E} 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

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.

Referências


Artigos relacionados