Type Guards no TypeScript: Guia Prático
Domine type guards no TypeScript. De typeof e instanceof até user-defined guards com is, tudo explicado com exemplos que você aplica no próximo PR.
O Que São Type Guards no TypeScript
Type guard é qualquer expressão que estreita (narrow) o tipo de uma variável dentro de um bloco de código. Quando o TypeScript vê um if (typeof x === 'string'), ele sabe que dentro daquele if, x é string. Isso é narrowing.
O TypeScript já vem com type guards nativos: typeof, instanceof e o operador in. Mas o mais poderoso são os user-defined type guards, onde você cria suas próprias funções de verificação usando a keyword is.
A ideia central é: você recebe um tipo amplo (como unknown ou uma union) e escreve lógica que comprova pro compilador qual subtipo ele é. A partir daí, o TypeScript confia em você e libera acesso a propriedades e métodos específicos daquele subtipo.
Sem type guards, você acaba usando as pra forçar tipos. E as não faz verificação nenhuma em runtime. Se o tipo tiver errado, seu código quebra silenciosamente. Type guards resolvem isso fazendo a checagem de fato.
Como Criar Type Guards Passo a Passo
Do mais simples ao mais sofisticado, cada técnica de narrowing tem seu lugar. Vamos percorrer todas.
Exemplos Práticos de Type Guards
Vamos ver cada tipo de guard funcionando em cenários que você encontra no dia a dia.
typeof: Narrowing de Primitivos
function formatar(valor: string | number | boolean): string {
if (typeof valor === "string") {
// TypeScript sabe: valor é string aqui
return valor.toUpperCase();
}
if (typeof valor === "number") {
// TypeScript sabe: valor é number aqui
return valor.toFixed(2);
}
// TypeScript sabe: valor é boolean aqui
return valor ? "Sim" : "Não";
}
console.log(formatar("hello")); // "HELLO"
console.log(formatar(3.14159)); // "3.14"
console.log(formatar(true)); // "Sim"
instanceof: Narrowing de Classes
class HttpError {
constructor(public status: number, public message: string) {}
}
class ValidationError {
constructor(public field: string, public reason: string) {}
}
function tratarErro(erro: HttpError | ValidationError) {
if (erro instanceof HttpError) {
// TypeScript sabe: erro é HttpError
console.log(`HTTP ${erro.status}: ${erro.message}`);
} else {
// TypeScript sabe: erro é ValidationError
console.log(`Campo ${erro.field}: ${erro.reason}`);
}
}
tratarErro(new HttpError(404, "Not Found"));
// "HTTP 404: Not Found"
tratarErro(new ValidationError("email", "Formato inválido"));
// "Campo email: Formato inválido"
Operador in: Verificar Propriedades
interface Carro {
marca: string;
portas: number;
}
interface Moto {
marca: string;
cilindradas: number;
}
function descrever(veiculo: Carro | Moto): string {
if ("portas" in veiculo) {
// TypeScript sabe: veiculo é Carro
return `${veiculo.marca} com ${veiculo.portas} portas`;
}
// TypeScript sabe: veiculo é Moto
return `${veiculo.marca} com ${veiculo.cilindradas}cc`;
}
console.log(descrever({ marca: "Honda", portas: 4 }));
// "Honda com 4 portas"
console.log(descrever({ marca: "Yamaha", cilindradas: 600 }));
// "Yamaha com 600cc"
User-Defined Type Guard com is
interface User {
id: number;
name: string;
email: string;
}
interface Admin extends User {
permissions: string[];
level: number;
}
// Type guard customizado com is
function isAdmin(user: User): user is Admin {
return "permissions" in user && "level" in user;
}
function exibirInfo(user: User) {
console.log(`Nome: ${user.name}`);
if (isAdmin(user)) {
// TypeScript sabe: user é Admin aqui
console.log(`Level: ${user.level}`);
console.log(`Permissões: ${user.permissions.join(", ")}`);
}
}
// Filtrando arrays com type guard
const usuarios: User[] = [
{ id: 1, name: "João", email: "joao@mail.com" },
{ id: 2, name: "Ana", email: "ana@mail.com",
permissions: ["read", "write"], level: 2 } as Admin,
];
// admins tem tipo Admin[] automaticamente!
const admins = usuarios.filter(isAdmin);
Assertion Functions com asserts
// Assertion function: lança erro se falhar
function assertString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error(`Expected string, got ${typeof value}`);
}
}
function assertNotNull<T>(value: T | null | undefined): asserts value is T {
if (value === null || value === undefined) {
throw new Error("Value is null or undefined");
}
}
// Uso prático
function processarDado(input: unknown) {
assertString(input);
// A partir daqui, input é string
console.log(input.toUpperCase());
}
function buscarUsuario(id: number) {
const user = db.find(u => u.id === id); // User | undefined
assertNotNull(user);
// A partir daqui, user é User
console.log(user.name);
}
Cada técnica tem seu espaço. typeof pra primitivos, instanceof pra classes, in pra checar propriedades, is pra lógica customizada e asserts pra validações que devem travar a execução. Use a ferramenta certa pro contexto certo.
Erros Comuns com Type Guards
Armadilhas que sabotam seu narrowing
Type guard com is que não verifica de verdade: se sua função isAdmin retorna true sem checar as propriedades, o TypeScript confia em você cegamente. Se o tipo tiver errado, o bug vai explodir em runtime. Sempre valide de fato.
typeof com null retorna 'object': essa é clássica. typeof null === 'object' é true em JavaScript. Sempre cheque null separadamente antes de usar typeof pra objetos.
instanceof não funciona com interfaces: interfaces não existem em runtime. Você não pode fazer value instanceof MinhaInterface. Use o operador in ou crie um user-defined type guard.
Narrowing que não persiste em callbacks: dentro de um if (typeof x === 'string'), se você passar x pra um callback assíncrono, o narrowing pode não ser preservado. Guarde o valor numa variável local.
Assertion function sem throw: se a assertion function não lança erro no caso negativo, o TypeScript estreita o tipo mesmo assim. Seu código parece seguro, mas não é. Sempre lance erro no caminho infeliz.
Checklist de Type Guards
TypeScript Avançado na Prática
Type guards são o alicerce de código TypeScript seguro e legível. Mas o poder de verdade aparece quando você combina narrowing com generics, discriminated unions e conditional types num projeto completo. No CrazyStack, você constrói um SaaS inteiro com TypeScript, Node.js e React, aplicando esses padrões em cenários reais de produção.
Se você quer parar de lutar contra o compilador e começar a deixar ele trabalhar a seu favor, esse é o próximo passo.
Continue lendo
Como Usar Infer no TypeScript
Extraia tipos automaticamente com a keyword infer dentro de conditional types.
Como Usar Discriminated Unions no TypeScript
Modele estados complexos com uniões discriminadas e exhaustive checks.
Como Usar Generics no TypeScript
Crie funções e tipos reutilizáveis com generics no TypeScript.
Interface no TypeScript
Quando usar interface