1

# 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.

  • isString verifica strings.
  • isNumber verifica números finitos.
  • arrayOf(isString) verifica arrays de strings.
  • struct verifica estruturas de objetos aninhados.
  • optionalKey permite 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?

MonstroLição de TypeScript
🧟 Mortos-vivos desconhecidosunknown impede operações sem verificação
🎃 Abóbora gigantestruct valida estruturas de objetos
🍬 Doces ou travessurasor combina type guards reutilizáveis
🧛 Conde Anyany 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! 🤲

Carregando publicação patrocinada...