unknown e type guards: validando o que vem de fora
Um JSON de uma API, um valor do localStorage, um texto digitado: nenhum deles chega com tipo garantido. O TypeScript tem uma ferramenta honesta para isso, o unknown.
Estudar esta lição interativa →No Módulo 2 você viu que o any é a armadilha da desistência: desliga a checagem de tipos. Mas há situações em que você de fato não sabe o tipo de um valor: o resultado de um JSON.parse, a resposta de um fetch, o que foi lido do localStorage. Para "recebi algo e ainda não sei o que é", o TypeScript oferece o unknown: ele aceita qualquer valor, mas não deixa usar o valor até você provar o que ele é.
// unknown: "recebi algo e ainda não sei o que é".
// Diferente do any, o TypeScript NÃO deixa usar o valor sem antes checar.
function descrever(valor: unknown): string {
// valor.toUpperCase(); // erro TS18046: 'valor' is of type 'unknown'
if (typeof valor === "string") return "texto: " + valor.toUpperCase();
if (typeof valor === "number") return "número: " + valor.toFixed(1);
return "algo que não sei usar ainda";
}
console.log(descrever("oi"));
console.log(descrever(3));
console.log(descrever(null));Descomente a linha valor.toUpperCase() para ver o erro TS18046.
Pense num pacote lacrado que chegou pelo correio. O any é abrir o pacote e usar o que estiver dentro sem olhar, torcendo para ser o que você esperava. O unknown é a regra do porteiro: ninguém usa o conteúdo até conferir a etiqueta. O typeof é a conferência; depois dela, dentro do if, o pacote deixa de ser misterioso.
Para objetos, o typeof sozinho não basta (todo objeto é "object"). A saída é escrever uma função que confere o formato e avisa o TypeScript do resultado, um type guard. O que a torna especial é o tipo de retorno valor is Usuario: ele promete que, se a função devolver true, o valor tem mesmo o formato de Usuario, e dentro do if que a chama o TypeScript passa a tratá-lo assim:
interface Usuario {
nome: string;
idade: number;
}
// Type guard: o "valor is Usuario" promete ao TypeScript que, se a função
// devolver true, o valor tem mesmo o formato de Usuario.
function ehUsuario(valor: unknown): valor is Usuario {
if (typeof valor !== "object" || valor === null) return false;
const campos = valor as Record<string, unknown>;
return typeof campos.nome === "string" && typeof campos.idade === "number";
}
// JSON.parse devolve any; guardamos como unknown para sermos obrigados a checar.
const bruto: unknown = JSON.parse('{"nome":"Ana","idade":30}');
if (ehUsuario(bruto)) {
console.log(bruto.nome.toUpperCase(), bruto.idade + 1); // aqui bruto é Usuario
} else {
console.log("dado inválido");
}
const incompleto: unknown = JSON.parse('{"nome":"Ana"}');
console.log(ehUsuario(incompleto)); // false: falta a idadeO compilador não confere se a sua checagem está completa: se a função disser true para um objeto sem idade, ele vai tratar o valor como Usuario e o erro só aparece rodando. Lembre-se também do Módulo 1: os tipos somem na compilação, então "este JSON é um Usuario" não é verificado por tipo nenhum, só por código que roda. Em projetos grandes, bibliotecas de validação (como o Zod) escrevem esses guards para você a partir de uma descrição do formato.