# Trick or Type Guard? Sobrevivendo ao `unknown` com is-kit 🎃
Hoi hoi! 👋
Eu sou @nyaomaru, um engenheiro frontend que morre de medo de coisas de terror. 🙀
Feliz Halloween! 🎃👻
Hoje à noite, vamos explorar um projeto TypeScript assombrado.
Temos zumbis 🧟, fantasmas 👻, abóboras misteriosas 🎃 e um vampiro aterrorizante 🧛 à nossa espera.
E todos eles têm alguma coisa a ver com tipos do TypeScript!
Nossa missão?
Sobreviver à noite sem perder a segurança de tipos! 😸
Vamos lá!
🧟 Capítulo 1: Os mortos-vivos desconhecidos
É uma noite escura e assustadora.
De repente, alguma coisa sai rastejando de uma sepultura! 🧟
const creature: unknown = JSON.parse('{"message":"BRAAAINS!"}');
Ah! Uma criatura desconhecida!
Vamos descobrir o que ela quer.
console.log(creature.message);
// ❌ 'creature' is of type 'unknown'.
O QUÊ?! 🙀
O TypeScript não deixa!
Mas por quê?
Porque, na verdade, não sabemos o que creature é.
Pode ser um zumbi. Pode ser um fantasma. Pode até ser um gatinho fofo. 😸
unknown significa que ainda não verificamos o valor.
Diferentemente de any, o TypeScript não permite acessar propriedades arbitrárias de um valor unknown.
E isso é bom!
Vamos tentar algo mais simples.
import { isString } from "is-kit";
const voice: unknown = JSON.parse('"BRAAAINS!"');
if (isString(voice)) {
console.log(voice.toUpperCase());
// BRAAAINS!
}
YAY! 🎉
Sobrevivemos ao primeiro encontro!
É claro que aqui também poderíamos usar typeof voice === 'string'.
Para uma única verificação, isso funciona perfeitamente!
Mas e quando nossos monstros ficarem maiores e mais complicados?
🎃 Capítulo 2: A abóbora gigante do desconhecido
Olha só!
UMA ABÓBORA GIGANTESCA! 🎃
E parece estar cheia de doces.
Só que ninguém sabe o que existe lá dentro de verdade.
const pumpkin: unknown = JSON.parse(`{
"name": "Big Pumpkin",
"candies": ["Chocolate", "Lollipop", "Cookie"],
"spookyLevel": 99,
"keeper": {
"name": "cat",
"friendly": true
},
"secret": "Meow!"
}`);
É um objeto bem grande.
Podemos simplesmente pegar os doces?
console.log(pumpkin.candies);
// ❌ 'pumpkin' is of type 'unknown'.
De novo não! 😿
Precisamos verificar o conteúdo antes de tocar em qualquer coisa.
Ainda bem que temos struct!
import {
arrayOf,
isBoolean,
isNumber,
isString,
optionalKey,
struct,
} from "is-kit";
const isPumpkin = struct({
name: isString,
candies: arrayOf(isString),
spookyLevel: isNumber,
keeper: struct({
name: isString,
friendly: isBoolean,
}),
secret: optionalKey(isString),
});
Combinamos vários guards pequenos em um único guard para a abóbora.
isStringverifica strings.isNumberverifica números finitos.arrayOf(isString)verifica arrays de strings.structverifica estruturas de objetos aninhados.optionalKeypermite que uma propriedade esteja ausente.
Agora vamos abrir a abóbora!
import { safeParse } from "is-kit";
const result = safeParse(isPumpkin, pumpkin);
if (result.valid) {
console.log(result.value.candies);
// ['Chocolate', 'Lollipop', 'Cookie']
console.log(result.value.keeper.name);
// cat 😸
}
Encontramos os doces!!! 🍬🎉
Quando a validação passa, result.value recebe o tipo inferido a partir dos nossos guards.
E quando a validação falha?
const cursedPumpkin = {
name: "Cursed Pumpkin",
candies: "100 candies",
spookyLevel: 99,
keeper: {
name: "cat",
friendly: true,
},
};
const result = safeParse(isPumpkin, cursedPumpkin);
console.log(result);
// { valid: false }
Os doces eram uma string, não um array!
Nosso guard rejeitou a abóbora amaldiçoada.
E aqui está o ponto importante: safeParse não corrige nem converte magicamente valores inválidos.
Ele simplesmente informa se o valor passou no guard.
Além disso, struct permite propriedades extras por padrão. Para rejeitar chaves string próprias e enumeráveis adicionais, podemos usar { exact: true }.
Simples assim!
🍬 Capítulo 3: Trick or Type Guard!
Ding dong! 🔔
DOCES OU TRAVESSURAS!!! 😸
Alguém nos entrega um presente misterioso.
function receiveGift(): unknown {
return Math.random() > 0.5 ? "Chocolate" : { spiders: 100 };
}
const gift: unknown = receiveGift();
Espera.
É um doce? Ou uma caixa cheia de aranhas?! 🕷️😱
Precisamos conferir!
Desta vez, queremos aceitar tanto uma string quanto um número.
Vamos combinar os guards com or!
import { isNumber, isString, or } from "is-kit";
const isTreat = or(isString, isNumber);
function trickOrTreat(gift: unknown) {
if (isTreat(gift)) {
// gift: string | number
return `🍬 Treat: ${gift}`;
}
return "👻 Trick!";
}
Agora vamos testar!
trickOrTreat("Chocolate");
// 🍬 Treat: Chocolate
trickOrTreat(100);
// 🍬 Treat: 100
trickOrTreat({ spiders: 100 });
// 👻 Trick!
YAY!!! 🍬
Nada de aranhas hoje!
Essa é uma das coisas de que mais gosto no is-kit.
Não precisamos escrever um validador gigante.
Podemos criar guards pequenos, combiná-los e reutilizá-los onde for necessário.
Guards pequenos. Composição simples. Narrowing útil no TypeScript.
Essa é a ideia!
Mas espere... alguma coisa está chegando.
Algo ATERRORIZANTE.
🧛 Chefe final: o vampiro chamado any
De repente, as luzes se apagam.
A porta se abre.
Uma figura misteriosa surge na escuridão.
É... CONDE ANY!!! 🧛💀
O vampiro mais assustador do nosso projeto TypeScript! E ele tem um poder perigosíssimo.
Ele consegue fazer o TypeScript parar de verificar nossas operações!
const visitor: unknown = JSON.parse('{"name":666}');
Um visitante misterioso chegou.
Normalmente, o TypeScript nos protege de acessar propriedades antes de verificá-las.
Mas o Conde Any tem um truque.
A MALDIÇÃO DO as any! 🧛
const vampire = visitor as any;
// The vampire ignores type safety!
console.log(vampire.name.toUpperCase());
Nenhum erro de TypeScript!
Espera, COMO ASSIM?! 🙀
Isso significa que o código é seguro?
Em runtime:
TypeError: vampire.name.toUpperCase is not a function
💀 GAME OVER 💀
O Conde Any não transformou o número em string.
Ele só convenceu o TypeScript a parar de verificar!
É isso que torna any perigoso: ele permite operações sem exigir provas de que vão funcionar.
Mas unknown se comporta de outro jeito.
const visitor: unknown = JSON.parse('{"name":666}');
console.log(visitor.name.toUpperCase());
// ❌ TypeScript stops us!
O TypeScript não deixa continuar até verificarmos o valor.
Então, como derrotar o Conde Any?
Com um Type Guard, é claro! 😸
import { isString, struct } from "is-kit";
const isFriendlyGuest = struct({
name: isString,
});
if (isFriendlyGuest(visitor)) {
console.log(visitor.name.toUpperCase());
} else {
console.log("🧄 Not tonight, Count Any!");
}
E assim...
O VAMPIRO FOI DERROTADO! 🧄🎉
Bem, pelo menos esse valor amaldiçoado em particular foi rejeitado.
Um Type Guard verifica o valor real em runtime, enquanto uma type assertion só muda o que o TypeScript acredita sobre ele.
Um detalhe importante: nem unknown nem any alteram o valor em runtime.
Eles são tipos do TypeScript.
A diferença está no que o compilador nos permite fazer antes de verificar o valor.
Por isso, unknown normalmente é a opção mais segura para dados não confiáveis.
😸 Epílogo: Sobrevivemos ao Halloween!
Que noite!
Encontramos os mortos-vivos desconhecidos, abrimos uma abóbora gigante, pegamos doces e derrotamos o Conde Any!
Então, o que aprendemos?
| Monstro | Lição de TypeScript |
|---|---|
| 🧟 Mortos-vivos desconhecidos | unknown impede operações sem verificação |
| 🎃 Abóbora gigante | struct valida estruturas de objetos |
| 🍬 Doces ou travessuras | or combina type guards reutilizáveis |
| 🧛 Conde Any | any ignora a verificação normal de tipos |
É claro que any nem sempre é maligno.
Às vezes ele é útil ao lidar com código legado ou integrações difíceis.
Mas com respostas de API, JSON parseado e outros dados não confiáveis, mantê-los como unknown até a validação geralmente é muito mais seguro.
E is-kit facilita a composição e reutilização dessas verificações!
Quer saber mais? Confira o guia oficial 👇
📖 Como validar unknown em TypeScript sem uma biblioteca de schema
E lembre-se:
O verdadeiro monstro não é unknown. É o any que ninguém verificou. 🧛
Feliz Halloween!!! 🎃👻
Se gostou dessa pequena aventura em TypeScript, conheça o is-kit: uma biblioteca leve, sem dependências, para construir e compor type guards reutilizáveis com narrowing natural.
Se gostar do projeto, uma ⭐ no GitHub deixará este gatinho muito feliz! 😸
Obrigado por ler! 🤲