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
Enter fullscreen mode Exit fullscreen mode
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 é
}
Enter fullscreen mode Exit fullscreen mode
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);
}
Enter fullscreen mode Exit fullscreen mode
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
}
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
}
}
Enter fullscreen mode Exit fullscreen mode
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);
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
}
}
Enter fullscreen mode Exit fullscreen mode
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);
}
Enter fullscreen mode Exit fullscreen mode
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
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
, 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));
}
Enter fullscreen mode Exit fullscreen mode
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
}
Enter fullscreen mode Exit fullscreen mode
!== para exclusão
function processar(valor: string | null) {
if (valor !== null) {
// ✅ null foi eliminado — só string
console.log(valor.toUpperCase());
}
}
Enter fullscreen mode Exit fullscreen mode
== 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());
}
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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());
}
}
Enter fullscreen mode Exit fullscreen mode
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[]
Enter fullscreen mode Exit fullscreen mode
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.
답글 남기기