any e unknown aceitam qualquer valor. A diferença está no que você pode fazer com o valor depois: any deixa fazer qualquer coisa e não verifica nada, enquanto unknown não deixa fazer quase nada até você provar o que o valor é.
Sem a linha @ts-expect-error, u.toUpperCase() é o erro de compilação TS18046. A verificação com typeof estreita u para string, e dentro desse bloco todos os métodos de string ficam disponíveis.
any vs unknown em resumo
any | unknown | |
|---|---|---|
| Aceita qualquer valor | sim | sim |
Atribuir a um string, number, ... | sim, sem verificação | não (TS2322) |
| Ler uma propriedade, chamar um método | sim, sem verificação | não (TS18046) |
| Chamar como função | sim | não |
Aritmética e comparação (x * 2, x + 1, x < 5) | sim | não (TS18046) |
| Precisa de verificação antes de usar | não | sim (typeof, instanceof, in, um type guard) |
| Efeito na verificação de tipos | desligada para aquele valor e tudo o que ele toca | mantida |
Em termos de teoria de tipos, unknown é o top type: todo tipo pode ser atribuído a ele, e ele só pode ser atribuído a unknown e any. O any é uma válvula de escape atribuível nos dois sentidos, a tudo exceto never.
any desliga a verificação de tipos
Um valor com tipo any recebe confiança cega. O compilador aceita erros de digitação, tipos errados e propriedades que faltam, e os erros aparecem em tempo de execução.
A saída mostra o problema: uma variável anotada como number guarda uma string, e a última linha lança Cannot read properties of undefined (reading 'city'). O any também se espalha. user.name é any, então qualquer valor calculado a partir dele é any, e um único valor sem tipo pode desligar a verificação longe de onde entrou.
unknown obriga a verificar antes
Com unknown, o compilador recusa toda operação até o código fazer o narrowing do valor. O narrowing usa verificações comuns de JavaScript, e dentro de cada ramo o valor tem o tipo verificado.
A verificação value === null precisa vir antes da verificação de objeto porque typeof null é "object". Depois de "id" in value, o TypeScript sabe que o objeto tem uma propriedade id, com tipo unknown, já que nada diz o que ela guarda. String() a transforma em texto de forma explícita; para usá-la como número, você a verificaria antes com typeof.
Você também pode pular a verificação com uma assertion, value as string, e o compilador aceita. Isso é uma promessa sem nenhuma verificação em tempo de execução por trás, então prefira uma verificação de verdade; veja type assertions.
Validando JSON com unknown
JSON.parse é declarado como retornando any, então o resultado dele desliga a verificação em silêncio. Anote o resultado como unknown e escreva um type guard que verifica o formato antes que o resto do código confie nele.
A primeira entrada imprime dark at 14px, a segunda é rejeitada porque falta fontSize. Com any, a segunda entrada teria passado como um Settings com fontSize undefined. Para schemas grandes, uma biblioteca de validação faz o mesmo trabalho e deriva o tipo para você.
noImplicitAny
O any não vem só de alguém escrevê-lo. Um parâmetro sem anotação e sem contexto para inferir também seria any. A opção noImplicitAny, que o strict liga, aponta isso como erro:
index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.
A correção é uma anotação, function double(x: number). Escrever x: any explicitamente também compila, e é essa a ideia: o any fica visível no código e pode ser procurado e revisado.
Por onde o any ainda entra
Mesmo com strict, estes casos produzem any sem que a palavra apareça no seu código:
| Origem | O que você recebe | O que fazer |
|---|---|---|
JSON.parse(text) | any | anotar como unknown e validar |
response.json() com os tipos do navegador (DOM) | Promise<any> | o mesmo que no JSON.parse (os tipos do fetch do próprio Node já retornam Promise<unknown>) |
| Um pacote sem definições de tipo | any nos imports (com erro, a menos que você adicione uma declaração) | instalar @types/... ou escrever um .d.ts |
value as any | any | usar um type guard ou uma assertion precisa |
catch (e) com useUnknownInCatchVariables desligado | any | o strict o torna unknown; mantenha assim |
A variável do catch é unknown com strict porque qualquer coisa pode ser lançada, não só objetos Error. Faça o narrowing com e instanceof Error antes de ler e.message.
Quando o any é aceitável
O any não é proibido, mas cada um é um ponto que o compilador deixa de proteger. Usos razoáveis:
- Migrar uma base de código JavaScript, em que o
anymarca o que ainda não foi tipado. - Código que o sistema de tipos não consegue expressar bem, mantido pequeno e atrás de uma assinatura de função tipada.
- Código de teste que passa dados inválidos de propósito.
Para todo o resto, unknown cobre o mesmo caso de "não sei esse tipo" mantendo as verificações. Muitos times impõem isso com a regra de lint @typescript-eslint/no-explicit-any. Um tipo relacionado, Record<string, unknown>, é a escolha usual para "algum objeto com valores desconhecidos".
Perguntas frequentes
Qual é a diferença entre any e unknown no TypeScript?
Os dois aceitam qualquer valor. Com any você pode fazer qualquer coisa com o valor (ler propriedades, chamá-lo, atribuí-lo a um number) e o compilador não verifica nada. Com unknown você não pode fazer quase nada até fazer o narrowing com uma verificação como typeof x === "string". O unknown mantém a verificação de tipos ligada, e por isso é a escolha mais segura.
Quando usar unknown em vez de any?
Sempre que o tipo de um valor não é conhecido em tempo de compilação: JSON parseado, dados de uma resposta de rede, um erro capturado, a entrada de uma função de validação. Tipe como unknown e faça o narrowing. Use any só em trabalho temporário de migração ou em código que o sistema de tipos não consegue descrever.
O que significa "Object is of type 'unknown'"?
Os erros TS18046 ('x' is of type 'unknown') e TS2571 (Object is of type 'unknown', usado quando o valor não é um nome simples, como load().id) significam que você usou um valor unknown como se ele tivesse um tipo específico, por exemplo lendo uma propriedade ou chamando um método. Verifique o tipo antes (typeof, instanceof, Array.isArray, in ou uma função type guard) e use o valor dentro do ramo com o tipo já estreitado.
Por que JSON.parse retorna any?
A declaração dele na biblioteca padrão diz parse(text: string, ...): any, porque o compilador não tem como saber o que uma string contém. O resultado desliga em silêncio a verificação de tudo o que ele toca. Anote-o: const data: unknown = JSON.parse(text) e valide antes de usar.
O que é noImplicitAny?
Uma opção do compilador, parte do strict, que aponta um erro quando uma declaração receberia em silêncio o tipo any por não ter anotação nem nada de onde inferir: TS7006 para um parâmetro, TS7005 ou TS7034 para uma variável cujo tipo não pode ser deduzido. Ela impede que o any apareça sem ninguém escrevê-lo. Um any explícito continua permitido.