Tipos básicos e inferência
TypeScript infere o tipo na maioria dos casos a partir da atribuição. Anotar explicitamente é mais útil em parâmetros de funções públicas, retornos não-óbvios e variáveis de domínio onde a intenção deve ser clara sem ler a implementação.
// Inferência automática — TypeScript deduz os tipos
const contagem = 0; // number
const nome = "Rafael"; // string
const ativo = true; // boolean
const lista = [1, 2, 3]; // number[]
const tupla = [1, "ok"] as const; // readonly [1, "ok"]
// Tipos primitivos explícitos
let status: string;
let total: number;
let habilitado: boolean;
// Tipos especiais
let nulo: null;
let indefinido: undefined;
// any — desabilita a checagem de tipos (evite)
let qualquerCoisa: any = 42;
qualquerCoisa = "string"; // sem erro — mas sem segurança
// unknown — recebe qualquer valor, mas exige narrowing antes de usar
let entrada: unknown = receberDaAPI();
if (typeof entrada === "string") {
console.log(entrada.toUpperCase()); // TypeScript sabe que é string aqui
}
// never — tipo de valores que nunca ocorrem
function lancarErro(msg: string): never {
throw new Error(msg);
}
// void — funções que não retornam valor significativo
function registrarLog(msg: string): void {
console.log(msg);
}
// Anotação explícita em funções — sempre anotar parâmetros e retorno
function somar(a: number, b: number): number {
return a + b;
}
// Arrow function tipada
const dobrar = (n: number): number => n * 2;
// Parâmetros opcionais e com valor padrão
function cumprimentar(nome: string, saudacao?: string): string {
return `${saudacao ?? "Olá"}, ${nome}!`;
}
// Arrays e tuplas
const tags: string[] = ["typescript", "frontend"];
const tags2: Array<string> = ["typescript", "frontend"]; // forma alternativa
const ponto: [number, number] = [10, 20];
const entrada2: [string, number, boolean] = ["id", 1, true];
// Tuplas com nomes (TypeScript 4.0+)
type EventoHttp = [metodo: string, url: string, status: number];Interfaces vs Type Aliases
interface descreve a forma de objetos e pode ser estendida e mesclada (declaration merging). type é mais versátil — suporta unions, intersections, tipos computados e literais.
// Interface — preferida para objetos, contratos de API, herança
interface Usuario {
id: string;
nome: string;
email: string;
avatarUrl?: string; // opcional com ?
readonly criadoEm: Date; // somente leitura — não pode ser reatribuído
}
// Estendendo interface
interface UsuarioAdmin extends Usuario {
permissoes: string[];
nivel: 1 | 2 | 3;
}
// Estendendo múltiplas interfaces
interface EntidadeAuditada extends Usuario, { atualizadoEm: Date } {}
// Declaration merging — interfaces com o mesmo nome são mescladas
// Útil para augmentar tipos de bibliotecas
interface Window {
APP_CONFIG: { apiUrl: string; version: string };
}
// Type alias — para unions, intersections e tipos computados
type Status = "pendente" | "pago" | "cancelado"; // union
type ID = string | number;
// Intersection — combina múltiplos tipos em um
type EntidadeBase = { id: string; criadoEm: Date };
type Produto = EntidadeBase & {
nome: string;
preco: number;
estoque: number;
};
// Type pode representar qualquer tipo, interface só objetos
type Primitivo = string | number | boolean;
type Callback = (err: Error | null, dados: unknown) => void;
type Mapa<K extends string> = { [key in K]: number };
/*
Quando usar cada um:
- Interface: objetos de domínio, contratos de classe, APIs públicas de biblioteca
- Type: unions, intersections, tipos utilitários, tipos computados, aliases de complexidade
- Ambos são equivalentes para objetos simples — prefira consistência no projeto
*/Union e Intersection Types
Unions expressam “um ou outro”; intersections expressam “ambos ao mesmo tempo”. Narrowing é a técnica de afunilar um union para um tipo específico.
type Tamanho = "pequeno" | "médio" | "grande";
type Resultado<T> = { ok: true; dados: T } | { ok: false; erro: string };
// Union com tipos complexos
type Notificacao =
| { tipo: "email"; destinatario: string; assunto: string }
| { tipo: "sms"; telefone: string }
| { tipo: "push"; deviceToken: string; payload: object };
function enviarNotificacao(n: Notificacao): void {
// Narrowing via discriminated union
switch (n.tipo) {
case "email":
enviarEmail(n.destinatario, n.assunto);
break;
case "sms":
enviarSms(n.telefone);
break;
case "push":
enviarPush(n.deviceToken, n.payload);
break;
}
}
// Intersection — combina propriedades de múltiplos tipos
type ConfigBase = { timeout: number; retries: number };
type ConfigAuth = { token: string; refreshUrl: string };
type ClienteHttp = ConfigBase & ConfigAuth;
// Distribuição de union em generics
// Quando T é um union, operações são distribuídas sobre cada membro
type ToArray<T> = T extends unknown ? T[] : never;
type Strings = ToArray<string | number>; // string[] | number[]
// Para evitar distribuição, use tupla
type ToArrayNaoDistribuido<T> = [T] extends [unknown] ? T[] : never;
type AmbosJuntos = ToArrayNaoDistribuido<string | number>; // (string | number)[]Literal Types e Template Literal Types
Tipos literais restringem um valor a um conjunto exato de strings, números ou booleans. Template literal types constroem novos tipos de string via interpolação.
// Literal types
type Direcao = "norte" | "sul" | "leste" | "oeste";
type CodigoHTTP = 200 | 201 | 400 | 401 | 403 | 404 | 500;
type Booleano = true | false; // equivalente a boolean
// const assertion — infere o tipo mais estreito possível
const config = {
host: "localhost",
porta: 3000,
} as const;
// config.host: "localhost" (literal), não string
// Template literal types
type EventoDOM = "click" | "focus" | "blur" | "change";
type HandlerEvento = `on${Capitalize<EventoDOM>}`;
// "onClick" | "onFocus" | "onBlur" | "onChange"
type CSSPropriedade = "margin" | "padding" | "border";
type CSSLado = "Top" | "Right" | "Bottom" | "Left";
type CSSPropriedadeExpandida = `${CSSPropriedade}${CSSLado}`;
// "marginTop" | "marginRight" | ... | "borderLeft"
// Casos práticos
type Rota = "/usuarios" | "/produtos" | "/pedidos";
type MetodoHTTP = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
type EndpointChave = `${MetodoHTTP} ${Rota}`;
// "GET /usuarios" | "POST /usuarios" | ...
// Interpolação com tipos genéricos
type Getter<T extends string> = `get${Capitalize<T>}`;
type Setter<T extends string> = `set${Capitalize<T>}`;
type Propriedades = "nome" | "email" | "idade";
type Getters = { [K in Propriedades as Getter<K>]: string };
// { getNome: string; getEmail: string; getIdade: string }Enums
Enums agrupam valores relacionados com nomes legíveis. const enum é inlinado pelo compilador — sem overhead de runtime. Enums de string são os mais seguros para uso em APIs e debug.
// Enum numérico — valores auto-incrementados a partir de 0
enum Direcao {
Norte, // 0
Sul, // 1
Leste, // 2
Oeste, // 3
}
// Enum com valores iniciais customizados
enum CodigoHTTP {
Ok = 200,
Criado = 201,
NaoEncontrado = 404,
ErroInterno = 500,
}
// String enum — mais seguro e legível em debug/logs/APIs
enum Status {
Pendente = "PENDENTE",
Pago = "PAGO",
Cancelado = "CANCELADO",
}
function processarPedido(status: Status): void {
if (status === Status.Pago) { ... }
}
// const enum — completamente inlinado, sem código gerado em runtime
const enum Permissao {
Ler = 1,
Gravar = 2,
Admin = 4,
}
const minhasPermissoes = Permissao.Ler | Permissao.Gravar; // compilado para: 1 | 2
/*
Problemas conhecidos com enums:
1. Enums numéricos permitem valor reverso: Direcao[0] === "Norte" — acesso inesperado
2. Não funcionam bem com isolatedModules (esbuild, Vite) — use string enums ou const
3. const enum com "isolatedModules" gera erro — prefira union de string literals
Alternativa moderna (preferida por muitos projetos):
*/
const STATUS = {
PENDENTE: "PENDENTE",
PAGO: "PAGO",
CANCELADO: "CANCELADO",
} as const;
type Status2 = typeof STATUS[keyof typeof STATUS];
// "PENDENTE" | "PAGO" | "CANCELADO"Generics básicos
Generics permitem escrever código que funciona com qualquer tipo, mantendo a segurança em tempo de compilação. O tipo genérico é inferido automaticamente na maioria dos casos.
// Função genérica
function primeiro<T>(arr: T[]): T | undefined {
return arr[0];
}
const n = primeiro([1, 2, 3]); // TypeScript infere T = number
const s = primeiro(["a", "b"]); // T = string
// Múltiplos tipos genéricos
function mapeamento<T, U>(arr: T[], fn: (item: T) => U): U[] {
return arr.map(fn);
}
const comprimentos = mapeamento(["a", "bb", "ccc"], s => s.length); // number[]
// Interface genérica
interface RespostaAPI<T> {
dados: T;
meta: {
total: number;
pagina: number;
porPagina: number;
};
erro?: string;
}
type RespostaUsuarios = RespostaAPI<Usuario[]>;
type RespostaProduto = RespostaAPI<Produto>;
// Classe genérica
class Pilha<T> {
private items: T[] = [];
empilhar(item: T): void {
this.items.push(item);
}
desempilhar(): T | undefined {
return this.items.pop();
}
get tamanho(): number {
return this.items.length;
}
}
const pilhaNumeros = new Pilha<number>();
pilhaNumeros.empilhar(1);
pilhaNumeros.empilhar(2);
pilhaNumeros.desempilhar(); // number | undefinedGenerics avançados — keyof, typeof, infer, conditional
Estes operadores permitem construir tipos derivados de outros tipos sem duplicação.
// keyof — extrai as chaves de um tipo como union
type ChavesUsuario = keyof Usuario; // "id" | "nome" | "email" | "avatarUrl" | "criadoEm"
// Função tipada com keyof
function obterPropriedade<T, K extends keyof T>(obj: T, chave: K): T[K] {
return obj[chave];
}
const usuario: Usuario = { id: "1", nome: "Rafael", email: "r@r.dev", criadoEm: new Date() };
const nomeUsuario = obterPropriedade(usuario, "nome"); // string — TypeScript sabe o tipo
// obterPropriedade(usuario, "senha"); // Erro de compilação
// typeof — captura o tipo de um valor existente
const configuracao = {
apiUrl: "https://api.rafael.dev",
versao: "1.0.0",
depuracao: false,
} as const;
type Configuracao = typeof configuracao;
// { readonly apiUrl: "https://api.rafael.dev"; readonly versao: "1.0.0"; readonly depuracao: false }
// infer — extrai um tipo de dentro de outro (usado em conditional types)
type TipoRetorno<T> = T extends (...args: unknown[]) => infer R ? R : never;
type TipoParametros<T> = T extends (...args: infer P) => unknown ? P : never;
type RetornoBuscarUsuario = TipoRetorno<typeof buscarUsuario>; // Usuario
type ParamsBuscarUsuario = TipoParametros<typeof buscarUsuario>; // [id: string]
// Desembrulhar Promise com infer
type DesembrulharPromise<T> = T extends Promise<infer U> ? U : T;
type ValorResolvidoPromise = DesembrulharPromise<Promise<Usuario>>; // Usuario
// Extrair tipo de elemento de array
type ElementoArray<T> = T extends (infer U)[] ? U : never;
type ElementoLista = ElementoArray<string[]>; // string
// Conditional types complexos
type EhFuncao<T> = T extends (...args: unknown[]) => unknown ? true : false;
type Resultado1 = EhFuncao<() => void>; // true
type Resultado2 = EhFuncao<string>; // false
// Constraints — T extends X restringe os tipos aceitos
function comprimento<T extends { length: number }>(valor: T): number {
return valor.length;
}
comprimento("hello"); // OK — string tem .length
comprimento([1, 2, 3]); // OK — array tem .length
comprimento({ length: 5 }); // OK — objeto com .length
// comprimento(42); // Erro — number não tem .lengthUtility Types completo
Utility types derivam novos tipos de existentes sem duplicar definições. Parte da biblioteca padrão do TypeScript.
interface Pedido {
id: string;
clienteId: string;
total: number;
status: "pendente" | "pago" | "cancelado";
notas?: string;
criadoEm: Date;
}
// Partial<T> — todos os campos tornam-se opcionais
type AtualizarPedidoDto = Partial<Pedido>;
// { id?: string; clienteId?: string; total?: number; ... }
// Required<T> — todos os campos tornam-se obrigatórios (remove ?)
type PedidoCompleto = Required<Pedido>;
// Readonly<T> — nenhum campo pode ser reatribuído
type PedidoImutavel = Readonly<Pedido>;
// pedidoImutavel.total = 100; // Erro de compilação
// Pick<T, K> — seleciona apenas os campos K
type ResumoPedido = Pick<Pedido, "id" | "total" | "status">;
// Omit<T, K> — remove os campos K
type CriarPedidoDto = Omit<Pedido, "id" | "criadoEm">;
// Record<K, V> — objeto com chaves K e valores V
type ContagemStatus = Record<Pedido["status"], number>;
// { pendente: number; pago: number; cancelado: number }
type CacheResposta = Record<string, { dados: unknown; expira: Date }>;
// Extract<T, U> — extrai da union T os tipos atribuíveis a U
type StatusAtivos = Extract<Pedido["status"], "pendente" | "pago">;
// "pendente" | "pago"
// Exclude<T, U> — remove da union T os tipos atribuíveis a U
type StatusFinais = Exclude<Pedido["status"], "pendente">;
// "pago" | "cancelado"
// NonNullable<T> — remove null e undefined da union
type IDSeguro = NonNullable<string | null | undefined>;
// string
// ReturnType<T> — extrai o tipo de retorno de uma função
async function buscarPedido(id: string): Promise<Pedido> { ... }
type PedidoBuscado = ReturnType<typeof buscarPedido>; // Promise<Pedido>
type PedidoResolvido = Awaited<ReturnType<typeof buscarPedido>>; // Pedido
// Parameters<T> — extrai parâmetros como tupla
type ParamsBuscarPedido = Parameters<typeof buscarPedido>; // [id: string]
// ConstructorParameters<T> — parâmetros do constructor
class Servico {
constructor(public url: string, public timeout: number) {}
}
type ParamsConstrutor = ConstructorParameters<typeof Servico>; // [url: string, timeout: number]
// InstanceType<T> — tipo da instância criada pelo constructor
type InstanciaServico = InstanceType<typeof Servico>; // Servico
// Awaited<T> — desembrulha Promises recursivamente
type Valor = Awaited<Promise<Promise<string>>>; // string
// ThisType<T> — define o tipo de this dentro de um objeto
const metodos: ThisType<{ nome: string }> & {
cumprimentar(): string;
} = {
cumprimentar() { return `Olá, ${this.nome}`; }
};
// Template Literal Utility Types
type Maiusculas = Uppercase<"hello">; // "HELLO"
type Minusculas = Lowercase<"HELLO">; // "hello"
type Capitalizado = Capitalize<"hello">; // "Hello"
type Descapitalizado = Uncapitalize<"Hello">; // "hello"Mapped Types
Mapped types iteram sobre as chaves de um tipo e criam novo tipo transformando cada chave ou valor.
// Forma básica: [K in keyof T]
type Opcional<T> = {
[K in keyof T]?: T[K];
};
type SomenteLeitura<T> = {
readonly [K in keyof T]: T[K];
};
// Modificadores: + e - adicionam ou removem readonly e ?
type Mutavel<T> = {
-readonly [K in keyof T]: T[K]; // remove readonly de todas as chaves
};
type Obrigatorio<T> = {
[K in keyof T]-?: T[K]; // remove ? de todas as chaves
};
// Remapeamento de chaves com as (TypeScript 4.1+)
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type GettersUsuario = Getters<{ nome: string; email: string }>;
// { getNome: () => string; getEmail: () => string }
// Filtrar chaves por tipo
type ChavesString<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
type ChavesStringUsuario = ChavesString<Usuario>;
// { id: string; nome: string; email: string; avatarUrl?: string }
// Eventos tipados a partir de um mapa
interface EventMap {
click: MouseEvent;
focus: FocusEvent;
keydown: KeyboardEvent;
}
type EventListeners = {
[K in keyof EventMap]: (event: EventMap[K]) => void;
};
// Validador gerado automaticamente
type Validadores<T> = {
[K in keyof T]: (valor: T[K]) => boolean | string;
};
type ValidadoresUsuario = Validadores<Pick<Usuario, "nome" | "email">>;
// { nome: (valor: string) => boolean | string; email: (valor: string) => boolean | string }Conditional Types e infer avançado
Conditional types criam tipos que dependem de uma condição. Combinados com infer, permitem extrair partes de tipos complexos.
// Sintaxe básica: T extends U ? TipoSe : TipoSenão
type EhString<T> = T extends string ? "sim" : "não";
type R1 = EhString<string>; // "sim"
type R2 = EhString<number>; // "não"
// Distributividade — aplicado a cada membro do union individualmente
type TiposArray<T> = T extends unknown ? T[] : never;
type Arrays = TiposArray<string | number>; // string[] | number[]
// Evitar distribuição com tupla
type TipoArrayUnico<T> = [T] extends [unknown] ? T[] : never;
type ArrayUnion = TipoArrayUnico<string | number>; // (string | number)[]
// Extraindo tipos com infer
type Desembrulhar<T> = T extends Array<infer Item> ? Item : T;
type Str = Desembrulhar<string[]>; // string
type Num = Desembrulhar<number>; // number (não é array)
// Extrair tipo de Promise
type Resolver<T> = T extends Promise<infer U> ? Resolver<U> : T; // recursivo
type Profundo = Resolver<Promise<Promise<string>>>; // string
// Extrair parâmetros de função específica
type SegundoParametro<T> =
T extends (arg1: unknown, arg2: infer P, ...args: unknown[]) => unknown
? P
: never;
type Segundo = SegundoParametro<(a: string, b: number, c: boolean) => void>; // number
// infer em tipos de objeto
type TipoPropriedade<T, K extends keyof T> =
T extends { [key in K]: infer V } ? V : never;
// Padrão de permissão — only allows keys where value matches type
type SomenteMetodos<T> = {
[K in keyof T as T[K] extends Function ? K : never]: T[K];
};Narrowing completo
Narrowing usa verificações de runtime para afunilar um tipo genérico para um específico. O TypeScript analisa o fluxo de controle para inferir o tipo mais específico possível em cada branch.
// typeof narrowing
function formatar(valor: string | number): string {
if (typeof valor === "string") {
return valor.toUpperCase();
}
return valor.toFixed(2); // TypeScript sabe que é number aqui
}
// instanceof narrowing
function tratarErro(err: unknown): string {
if (err instanceof Error) {
return err.message; // err é Error aqui
}
if (err instanceof Response) {
return `HTTP ${err.status}`; // err é Response aqui
}
return String(err);
}
// in operator — verifica se propriedade existe
type Circulo = { tipo: "circulo"; raio: number };
type Retangulo = { tipo: "retangulo"; largura: number; altura: number };
type Forma = Circulo | Retangulo;
function calcularArea(forma: Forma): number {
if ("raio" in forma) {
return Math.PI * forma.raio ** 2;
}
return forma.largura * forma.altura;
}
// Discriminated union — propriedade literal única
type EventoApp =
| { tipo: "USUARIO_LOGOU"; payload: { usuarioId: string } }
| { tipo: "PEDIDO_CRIADO"; payload: { pedidoId: string; total: number } }
| { tipo: "PRODUTO_ATUALIZADO"; payload: { produtoId: string } };
function processarEvento(evento: EventoApp): void {
switch (evento.tipo) {
case "USUARIO_LOGOU":
// evento.payload.usuarioId disponível
break;
case "PEDIDO_CRIADO":
// evento.payload.pedidoId e total disponíveis
break;
case "PRODUTO_ATUALIZADO":
// evento.payload.produtoId disponível
break;
default:
// never — TypeScript garante que todos os casos foram cobertos
const _exhaustivo: never = evento;
}
}
// Type predicate — função que ensina o TypeScript a fazer narrowing
function ehPedido(valor: unknown): valor is Pedido {
return (
typeof valor === "object" &&
valor !== null &&
"id" in valor &&
"total" in valor &&
typeof (valor as Pedido).total === "number"
);
}
const dados: unknown = receberDaAPI();
if (ehPedido(dados)) {
console.log(dados.total); // TypeScript sabe que é Pedido
}
// Assertion functions — lançam exceção se a condição for falsa
function garantir<T>(valor: T | null | undefined, msg: string): asserts valor is T {
if (valor === null || valor === undefined) {
throw new Error(msg);
}
}
const usuario2 = await buscarUsuario("123"); // Usuario | null
garantir(usuario2, "Usuário não encontrado");
console.log(usuario2.nome); // TypeScript sabe que não é null após garantir()
// Inferência automática de type predicates (TypeScript 5.5+)
// Antes do 5.5, era necessário escrever `: valor is T` manualmente
const numeros = [1, 2, null, 3, null].filter(Boolean); // number[] (inferido automaticamente)Classes
TypeScript adiciona modificadores de acesso, propriedades de readonly, classes abstratas e decorators às classes JavaScript.
// Modificadores de acesso
class ContaBancaria {
public titular: string; // acessível de qualquer lugar (padrão)
protected saldo: number; // acessível na classe e subclasses
private historico: string[]; // acessível apenas na própria classe
readonly numeroConta: string; // não pode ser reatribuído após inicialização
// Shorthand de constructor — cria e atribui as propriedades automaticamente
constructor(
public readonly banco: string,
protected agencia: string,
titular: string,
saldoInicial: number = 0
) {
this.titular = titular;
this.saldo = saldoInicial;
this.historico = [];
this.numeroConta = crypto.randomUUID();
}
depositar(valor: number): void {
this.saldo += valor;
this.historico.push(`+${valor}`);
}
get saldoAtual(): number {
return this.saldo;
}
}
// Classe abstrata — define contrato, não pode ser instanciada
abstract class Repositorio<T extends { id: string }> {
abstract buscarPorId(id: string): Promise<T | null>;
abstract listar(): Promise<T[]>;
abstract salvar(entidade: T): Promise<T>;
abstract deletar(id: string): Promise<void>;
// Método concreto disponível para subclasses
async buscarOuFalhar(id: string): Promise<T> {
const entidade = await this.buscarPorId(id);
if (!entidade) throw new Error(`Entidade ${id} não encontrada`);
return entidade;
}
}
// Implementação concreta
class RepositorioPedidos extends Repositorio<Pedido> {
private pedidos: Map<string, Pedido> = new Map();
async buscarPorId(id: string): Promise<Pedido | null> {
return this.pedidos.get(id) ?? null;
}
// ... implementações dos outros métodos
}
// Implements — valida que a classe segue um contrato (interface)
interface ServicoEmail {
enviar(para: string, assunto: string, corpo: string): Promise<void>;
}
class ServicoEmailSendGrid implements ServicoEmail {
async enviar(para: string, assunto: string, corpo: string): Promise<void> {
// implementação...
}
}
// override — documenta e valida que está sobrescrevendo método da superclasse
class ContaPoupanca extends ContaBancaria {
private rendimento: number;
constructor(banco: string, agencia: string, titular: string, taxa: number) {
super(banco, agencia, titular, 0);
this.rendimento = taxa;
}
override depositar(valor: number): void {
super.depositar(valor);
console.log(`Rendimento de ${this.rendimento}% aplicado`);
}
}Decorators (TypeScript 5.0 — Stage 3)
Decorators são funções que envolvem classes, métodos, acessores, campos e parâmetros para adicionar comportamento. No TypeScript 5.0+, seguem a especificação Stage 3 — diferente e incompatível com experimentalDecorators.
// Decorator de classe — recebe o constructor e contexto
function Singleton<T extends new (...args: unknown[]) => unknown>(
Base: T,
ctx: ClassDecoratorContext
) {
let instancia: InstanceType<T>;
return class extends Base {
constructor(...args: unknown[]) {
super(...args);
if (!instancia) instancia = this as InstanceType<T>;
return instancia;
}
} as T;
}
// Decorator de método — recebe a função e contexto
function Memoizar<T extends (...args: unknown[]) => unknown>(
metodo: T,
ctx: ClassMethodDecoratorContext
) {
const cache = new Map<string, ReturnType<T>>();
return function (this: unknown, ...args: Parameters<T>): ReturnType<T> {
const chave = JSON.stringify(args);
if (cache.has(chave)) return cache.get(chave)!;
const resultado = metodo.apply(this, args) as ReturnType<T>;
cache.set(chave, resultado);
return resultado;
};
}
// Decorator de acessor
function Validar<T>(
original: ClassAccessorDecoratorTarget<unknown, T>,
ctx: ClassAccessorDecoratorContext
) {
return {
set(this: unknown, valor: T) {
if (typeof valor === "string" && valor.trim() === "") {
throw new Error(`${String(ctx.name)} não pode ser vazio`);
}
original.set.call(this, valor);
},
};
}
// Decorator de campo
function PadraoZero(
_: undefined,
ctx: ClassFieldDecoratorContext
) {
return function (this: unknown) {
return 0;
};
}
// Uso
@Singleton
class ConfiguracaoApp {
@Validar
accessor nomeAplicacao = "Meu App";
@PadraoZero
contadorRequisicoes!: number;
@Memoizar
calcularHash(input: string): string {
return btoa(input); // exemplo simplificado
}
}Module System e Declaration Files
TypeScript oferece controle fino sobre como módulos são importados e exportados. import type garante que importações de tipos sejam removidas em tempo de compilação.
// import type — importa apenas tipos (zero runtime overhead)
import type { Usuario, Pedido } from "./tipos";
import type { FC, ReactNode } from "react";
// verbatimModuleSyntax (tsconfig) — obriga uso de import type para tipos
// Com esta flag, o compilador errou se você importar tipo sem "type"
// Re-exportar tipos
export type { Usuario } from "./usuario";
export { processarPedido } from "./pedido";
// Importação com alias
import { buscarUsuario as fetchUser } from "./api/usuario";
// Namespace import
import * as ApiUsuario from "./api/usuario";
// Dynamic import — carrega módulo sob demanda
const { parse } = await import("./lib/parse-csv");
const modulo = await import(`./templates/${nome}-template`);
// esModuleInterop — permite importar módulos CommonJS como default
// Com flag habilitada no tsconfig:
import express from "express"; // em vez de: import * as express from "express"Declaration files (.d.ts) — tipando bibliotecas sem tipos:
// biblioteca-sem-tipos.d.ts
// Declaração de módulo externo
declare module "biblioteca-legada" {
export function processar(input: string): string;
export interface Opcoes {
modo: "rapido" | "lento";
limite?: number;
}
}
// Augmentation — estender tipos de módulos externos
declare module "express" {
interface Request {
usuarioLogado?: { id: string; email: string };
}
}
// Declarar variáveis globais (injetadas por bundler/HTML)
declare const __DEV__: boolean;
declare const __APP_VERSION__: string;
// Triple-slash directive — referência explícita a outro arquivo de tipos
/// <reference types="vite/client" />
/// <reference path="./tipos-extras.d.ts" />tsconfig.json completo
O tsconfig.json controla o compilador TypeScript. As opções de strict são as mais importantes — habilitam um conjunto de verificações que previnem classes inteiras de bugs.
{
"compilerOptions": {
// ── Target e módulos ──
"target": "ES2022",
// Versão JS de saída: ES5, ES6, ES2017, ES2022, ESNext
// Afeta quais polyfills são gerados
"module": "ESNext",
// Formato de módulos: CommonJS, ESNext, Node16, NodeNext
"moduleResolution": "bundler",
// Como resolver imports: "bundler" para Vite/esbuild, "node16" para Node.js puro
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// APIs disponíveis para o compilador verificar
// ── Strict mode — ative todos ──
"strict": true,
// Ativa: noImplicitAny, strictNullChecks, strictFunctionTypes,
// strictBindCallApply, strictPropertyInitialization, noImplicitThis,
// alwaysStrict, useUnknownInCatchVariables
"noUncheckedIndexedAccess": true,
// arr[0] é T | undefined, não T — previne erros de acesso fora do bounds
"exactOptionalPropertyTypes": true,
// Distingue { x?: string } (ausente) de { x: string | undefined } (presente mas undefined)
"noImplicitOverride": true,
// Obriga usar "override" ao sobrescrever métodos da superclasse
"noPropertyAccessFromIndexSignature": true,
// Obriga notação de colchetes para acesso em índice: obj["chave"] vs obj.chave
// ── Paths e aliases ──
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@componentes/*": ["./src/componentes/*"],
"@utils/*": ["./src/utils/*"],
"@tipos/*": ["./src/tipos/*"]
},
// ── Saída ──
"outDir": "./dist",
"rootDir": "./src",
"declaration": true, // gera arquivos .d.ts
"declarationMap": true, // mapeia .d.ts para o .ts original
"sourceMap": true, // gera sourcemaps para debug
"removeComments": false,
// ── Compatibilidade e interop ──
"esModuleInterop": true, // default imports para módulos CJS
"allowSyntheticDefaultImports": true,
"verbatimModuleSyntax": true, // obriga import type para tipos
"isolatedModules": true, // compatível com esbuild/swc/Vite
// ── References (projeto monorepo) ──
"composite": true, // habilita project references
"incremental": true, // cache de compilação
// ── Experimental ──
"experimentalDecorators": false, // NÃO usar com Stage 3 decorators
"emitDecoratorMetadata": false // somente para decorators legados
},
"include": ["src"],
"exclude": ["node_modules", "dist", "**/*.test.ts"],
"references": [
{ "path": "./pacotes/core" },
{ "path": "./pacotes/ui" }
]
}Padrões avançados
Builder Pattern tipado
O builder pattern com TypeScript permite construir objetos complexos com validação de tipo em tempo de compilação — garantindo que todos os campos obrigatórios sejam fornecidos antes de chamar build().
// Opaque types / Branding — tipos nominais em TypeScript estrutural
type Marcado<T, Marca extends string> = T & { readonly _marca: Marca };
type UsuarioID = Marcado<string, "UsuarioID">;
type PedidoID = Marcado<string, "PedidoID">;
type EmailValido = Marcado<string, "EmailValido">;
// Criadores — única forma de criar os tipos marcados
const criarUsuarioID = (id: string): UsuarioID => id as UsuarioID;
const criarEmailValido = (email: string): EmailValido => {
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
throw new Error("E-mail inválido");
}
return email as EmailValido;
};
// Agora não é possível passar um PedidoID onde UsuarioID é esperado
function buscarPedidosDoUsuario(id: UsuarioID): Promise<Pedido[]> { ... }
// buscarPedidosDoUsuario("abc"); // Erro — string não é UsuarioID
// buscarPedidosDoUsuario(criarPedidoID("x")); // Erro — PedidoID não é UsuarioID
buscarPedidosDoUsuario(criarUsuarioID("1")); // OK
// Builder pattern tipado — campos obrigatórios como generics
class ConstrutorRequisicao<
TModo extends "GET" | "POST" | "PUT" | "DELETE" | undefined = undefined,
TURL extends string | undefined = undefined
> {
private config: {
modo?: TModo;
url?: TURL;
cabecalhos: Record<string, string>;
corpo?: unknown;
} = { cabecalhos: {} };
setModo<M extends "GET" | "POST" | "PUT" | "DELETE">(
modo: M
): ConstrutorRequisicao<M, TURL> {
this.config.modo = modo as M;
return this as unknown as ConstrutorRequisicao<M, TURL>;
}
setURL<U extends string>(url: U): ConstrutorRequisicao<TModo, U> {
this.config.url = url as U;
return this as unknown as ConstrutorRequisicao<TModo, U>;
}
adicionarCabecalho(chave: string, valor: string): this {
this.config.cabecalhos[chave] = valor;
return this;
}
// build() só disponível quando modo E url foram definidos
build(
this: ConstrutorRequisicao<
"GET" | "POST" | "PUT" | "DELETE",
string
>
): Request {
return new Request(this.config.url!, {
method: this.config.modo,
headers: this.config.cabecalhos,
});
}
}
// Uso:
const req = new ConstrutorRequisicao()
.setModo("GET")
.setURL("/api/usuarios")
.adicionarCabecalho("Authorization", "Bearer abc")
.build(); // OK — modo e URL estão definidos
// new ConstrutorRequisicao().build(); // Erro de compilaçãoType-safe Event Emitter
// Emitter tipado — garante que eventos e handlers correspondam
type HandlerEvento<T> = (payload: T) => void;
class EmissorEventos<TMapaEventos extends Record<string, unknown>> {
private handlers = new Map<
keyof TMapaEventos,
Set<HandlerEvento<unknown>>
>();
on<K extends keyof TMapaEventos>(
evento: K,
handler: HandlerEvento<TMapaEventos[K]>
): () => void {
if (!this.handlers.has(evento)) {
this.handlers.set(evento, new Set());
}
this.handlers.get(evento)!.add(handler as HandlerEvento<unknown>);
// Retorna função para remover o listener
return () => this.off(evento, handler);
}
off<K extends keyof TMapaEventos>(
evento: K,
handler: HandlerEvento<TMapaEventos[K]>
): void {
this.handlers.get(evento)?.delete(handler as HandlerEvento<unknown>);
}
emit<K extends keyof TMapaEventos>(evento: K, payload: TMapaEventos[K]): void {
this.handlers.get(evento)?.forEach(h => h(payload));
}
}
// Definição do mapa de eventos
interface EventosSistema {
"usuario:logado": { usuario: Usuario; timestamp: Date };
"pedido:criado": { pedido: Pedido };
"erro:capturado": { erro: Error; contexto: string };
}
const emitter = new EmissorEventos<EventosSistema>();
// TypeScript valida evento e payload automaticamente
const removerListener = emitter.on("usuario:logado", ({ usuario, timestamp }) => {
console.log(`${usuario.nome} logou às ${timestamp.toISOString()}`);
});
emitter.emit("usuario:logado", {
usuario: { id: "1", nome: "Rafael", email: "r@r.dev", criadoEm: new Date() },
timestamp: new Date(),
});
// emitter.emit("evento:inexistente", {}); // Erro de compilação
// emitter.on("pedido:criado", ({ pedidoId }) => {}); // Erro — pedidoId não existeVersões — TypeScript 4.0 ao 5.5
| Versão | Ano | Principais novidades |
|---|---|---|
| 4.0 | 2020 | Variadic tuple types ([...T, ...U]); labeled tuple elements; unknown em catch blocks por padrão (com flag); editor performance |
| 4.1 | 2020 | Template literal types; remapeamento de chaves em mapped types (as); recursive conditional types |
| 4.2 | 2021 | Elementos rest no meio de tuplas; abstract em construct signatures; melhores mensagens de erro |
| 4.4 | 2021 | Análise de controle de fluxo para condições; useUnknownInCatchVariables como parte de strict |
| 4.5 | 2021 | Awaited<T> utility type; type modifier em named imports (import { type Foo }); tail-recursion elimination para conditional types |
| 4.7 | 2022 | Suporte nativo a ESM no Node.js ("module": "node16"); instantiation expressions; infer extends |
| 4.9 | 2022 | Operador satisfies — valida shape sem alterar o tipo inferido; in narrowing melhorado para tipos não-primitivos |
| 5.0 | 2023 | Decorators Stage 3 — reescritos e incompatíveis com experimentalDecorators; const type parameters (<const T>); --moduleResolution bundler; suporte a múltiplos arquivos de configuração em extends |
| 5.1 | 2023 | Tipos de retorno relacionados em getters/setters (get x(): string | undefined / set x(v: string)); JSDoc @param inline; velocidade de checagem melhorada |
| 5.2 | 2023 | using / await using (Explicit Resource Management — Stage 3); decorator metadata; array destructuring narrowing |
| 5.3 | 2023 | import attributes (import x from "y" with { type: "json" }); narrowing em comparações de tipo por fluxo de controle |
| 5.4 | 2024 | NoInfer<T> utility type (impede inferência em posição específica); melhorias em Object.groupBy e Map.groupBy; import.meta.dirname/filename para ESM Node |
| 5.5 | 2024 | Type predicates inferidos automaticamente para arrow functions de filtragem; --isolatedDeclarations para builds paralelas de .d.ts; ${configDir} em paths do tsconfig |