Introdução
- Type guards → provam o tipo explicitamente
- Narrowing → é o processo mais amplo, que inclui type guards e outras formas de afunilar
Narrowing
├── Type Guards
│ ├── typeof
│ ├── instanceof
│ ├── in
│
├── Equality Narrowing
│ ├── === / !==
│ └── == / != (loose, menos comum)
│
└── Truthiness Narrowing
├── if (x)
├── x && ...
└── !!x
Type Guards
Um type guard é qualquer expressão que, quando avaliada como true, garante ao TypeScript qual é o tipo dentro daquele bloco.
O problema que eles resolvem
type Resultado = { dados: string[] } | { mensagem: string };
function tratar(r: Resultado) {
console.log(r.dados); // ❌ erro — TypeScript não sabe qual dos dois é
}
Você precisa provar ao compilador qual tipo está lidando. Type guards fazem isso.
Typeof
O typeof é um operador do próprio JavaScript, o TypeScript apenas o observa e usa o resultado para afunilar o tipo.
O typeof funciona para estruturas primitivas como: string, number, boolean, undefined, symbol, bigint
O que acontece no exemplo passo a passo
function formatar(valor: string | number) {
// aqui: valor é string | number
if (typeof valor === "string") {
// aqui: TypeScript SABE que é string
// métodos de string disponíveis ✅
return valor.toUpperCase();
}
// aqui: TypeScript ELIMINOU string da union
// só sobrou number ✅
return valor.toFixed(2);
}
Esse "sobrar" é o narrowing acontecendo. O TypeScript rastreia o fluxo e descarta o que foi provado impossível. Por isso o nome traduzido é “estreitamento”, os tipos das variáveis vão se estreitando a medida que o typescript vai elimitando os tipos.
O typeof não distingue objetos entre si:
type NFe = { chaveAcesso: string };
type NFSe = { codigoVerificacao: string };
function processar(nota: NFe | NFSe) {
if (typeof nota === "object") {
// TS ainda não sabe se é NFe ou NFSe ❌
// typeof não ajuda aqui
}
}
Para objetos você precisa do in, instanceof, ou um user-defined guard — que são os outros type guards que veremos em seguida.
Instanceof
O instanceof verifica se um objeto foi criado por uma determinada classe — tecnicamente, se o prototype da classe aparece na cadeia de prototypes do objeto.
Sintaxe básica:
objeto instanceof Classe
Retorna true ou false, e o TypeScript usa isso para afunilar o tipo.
Exemplo simples
class ErroDeRede extends Error {
statusCode: number;
constructor(msg: string, code: number) {
super(msg);
this.statusCode = code;
}
}
class ErroDeValidacao extends Error {
campo: string;
constructor(msg: string, campo: string) {
super(msg);
this.campo = campo;
}
}
function tratar(err: Error) {
if (err instanceof ErroDeRede) {
console.log(err.statusCode); // ✅ TypeScript sabe que é ErroDeRede
}
if (err instanceof ErroDeValidacao) {
console.log(err.campo); // ✅ TypeScript sabe que é ErroDeValidacao
}
}
Passo a passo do Narrowing
function tratar(err: ErroDeRede | ErroDeValidacao) {
// aqui: err é ErroDeRede | ErroDeValidacao
if (err instanceof ErroDeRede) {
// aqui: err é ErroDeRede ✅
return;
}
// aqui: TypeScript eliminou ErroDeRede
// só sobrou ErroDeValidacao ✅
console.log(err.campo);
}
Só funciona com classes — não funciona com type ou interface:
type NFe = { chaveAcesso: string };
const nota = { chaveAcesso: "123" };
nota instanceof NFe // ❌ erro de compilação — NFe não é uma classe
Para objetos simples tipados com type ou interface, você usa in ou um user-defined guard com is.
IN
O in verifica se uma propriedade existe em um objeto. É o tipo guard ideal para distinguir entre tipos que são objetos literais (type ou interface), onde instanceof não funciona.
Sintaxe básica
"propriedade" in objeto
Retorna true se a propriedade existe no objeto ou em seu prototype.
Exemplo simples
type Sucesso = { dados: string[] };
type Erro = { mensagem: string };
function tratar(r: Sucesso | Erro) {
if ("dados" in r) {
console.log(r.dados); // ✅ TypeScript sabe que é Sucesso
} else {
console.log(r.mensagem); // ✅ TypeScript sabe que é Erro
}
}
Passo a passo do narrowing
function tratar(r: Sucesso | Erro) {
// aqui: r é Sucesso | Erro
if ("dados" in r) {
// aqui: r é Sucesso ✅
return;
}
// aqui: TypeScript eliminou Sucesso
// só sobrou Erro ✅
console.log(r.mensagem);
}
Narrowing
Narrowing é o processo pelo qual o TypeScript reduz um tipo amplo para um mais específico conforme analisa o fluxo do código. Ele não é uma feature isolada — é um comportamento contínuo do compilador.
Alguns desenvolvedores confudem O Type Guard com o Narrow. Podemos dizer que o Type Guard é uma ferramenta que dispara o narrowing
function processar(valor: string | number) {
// aqui: string | number
if (typeof valor === "string") {
// aqui: string
}
// aqui: number
}
O TypeScript rastreia o que é possível em cada ponto do código e descarta o que foi provado impossível.
Todas as formas de narrowing juntas
Narrowing
├── typeof → primitivos
├── instanceof → classes
├── in → objetos literais
├── User-defined → lógica customizada (is)
├── Equality → === / !== / == null
├── Truthiness → if (x) / !!x
└── Exhaustiveness → assertNever
, isCada uma é só uma ferramenta diferente para provar ao compilador qual tipo está presente.
O compilador acumula evidências
O narrowing é cumulativo, cada verificação elimina possibilidades:
function processar(valor: string | number | null | undefined) {
if (valor == null) return;
// eliminou null e undefined → string | number
if (typeof valor === "string") return;
// eliminou string → number
// aqui: só number ✅
console.log(valor.toFixed(2));
}
Equality
O TypeScript observa comparações de igualdade e usa o resultado para afunilar o tipo. Existem dois operadores: o estrito (=== / !==) e o loose (== / !=).
=== estrito
Compara valor e tipo — o mais comum:
function processar(x: string | number) {
if (x === "pendente") {
// ✅ x é string — e especificamente "pendente"
return x.toUpperCase();
}
// aqui: number
}
!== para exclusão
function processar(valor: string | null) {
if (valor !== null) {
// ✅ null foi eliminado — só string
console.log(valor.toUpperCase());
}
}
== loose — o único caso útil
Normalmente evitamos == em TypeScript, mas há um caso onde ele é genuinamente melhor: eliminar null e undefined ao mesmo tempo.
function processar(valor: string | null | undefined) {
// === exigiria duas verificações:
if (valor !== null && valor !== undefined) { ... }
// == faz os dois de uma vez:
if (valor != null) {
// ✅ null e undefined eliminados — só string
console.log(valor.toUpperCase());
}
}
Isso funciona porque null == undefined é true em JavaScript — os dois se cancelam mutuamente com ==.
Truthiness
Truthiness narrowing acontece quando o TypeScript observa se um valor é truthy ou falsy dentro de uma condição e afunila o tipo com base nisso.
Valores falsy em JavaScript
Esses são os únicos valores que avaliam como false num if:
false
0
-0
0n // bigint zero
"" // string vazia
null
undefined
NaN
Tudo o mais é truthy — incluindo [], {}, "false", -1.
Exemplo básico
function processar(valor: string | null) {
if (valor) {
// ✅ null foi eliminado — só string
console.log(valor.toUpperCase());
}
}
Type Predicates — guards customizados
Quando nenhum guard nativo basta, você cria o seu com a sintaxe param is Tipo. O TypeScript confia na sua implementação e estreita o tipo em todo lugar que a função for chamada.
// Sintaxe: retorno "param is Tipo" em vez de boolean
function isString(v: unknown): v is string {
return typeof v === "string";
}
// Caso real: validar shape de objeto externo
type NotaFiscal = { chave: string; valor: number; cnpj: string };
function isNotaFiscal(obj: unknown): obj is NotaFiscal {
return (
typeof obj === "object" && obj !== null &&
"chave" in obj && typeof (obj as any).chave === "string" &&
"valor" in obj && typeof (obj as any).valor === "number" &&
"cnpj" in obj && typeof (obj as any).cnpj === "string"
);
}
// Uso — TypeScript estreita o tipo automaticamente
function processarPayload(raw: unknown) {
if (!isNotaFiscal(raw)) throw new Error("Payload inválido");
// ✅ raw é NotaFiscal aqui — .chave, .valor, .cnpj disponíveis
console.log(raw.chave, raw.valor);
}
// Filtrando arrays com predicate (remove undefined do tipo)
const ids: (string | undefined)[] = ["a", undefined, "b"];
const validos: string[] = ids.filter(
(x): x is string => x !== undefined
); // ✅ tipo inferido como string[]
No fundo, tudo que vimos — typeof, instanceof, in, is, equality, truthiness — são apenas formas diferentes de fornecer evidências para o compilador afunilar o tipo com segurança.
Top comments (0)