any e unknown accettano entrambi qualsiasi valore. La differenza sta in cosa puoi fare con il valore dopo: any ti lascia fare tutto e non controlla nulla, mentre unknown non ti lascia fare quasi nulla finché non dimostri cos'è il valore.
Senza la riga @ts-expect-error, u.toUpperCase() è l'errore di compilazione TS18046. Il controllo typeof restringe u a string, e dentro quel blocco tutti i metodi delle stringhe sono disponibili.
any vs unknown in breve
any | unknown | |
|---|---|---|
| Accetta qualsiasi valore | sì | sì |
Assegnarlo a string, number, ... | sì, senza controlli | no (TS2322) |
| Leggere una proprietà, chiamare un metodo | sì, senza controlli | no (TS18046) |
| Chiamarlo come funzione | sì | no |
Aritmetica e confronti (x * 2, x + 1, x < 5) | sì | no (TS18046) |
| Serve un controllo prima dell'uso | no | sì (typeof, instanceof, in, una type guard) |
| Effetto sul controllo dei tipi | disattivato per quel valore e tutto ciò che tocca | resta attivo |
In termini di teoria dei tipi, unknown è il top type: ogni tipo gli è assegnabile, e lui è assegnabile solo a unknown e any. any è una via di fuga assegnabile in entrambe le direzioni, a tutto tranne che a never.
any disattiva il controllo dei tipi
Un valore tipizzato any riceve fiducia cieca. Il compilatore accetta errori di battitura, tipi sbagliati e proprietà mancanti, e gli errori saltano fuori a runtime.
L'output mostra il problema: una variabile annotata number contiene una stringa, e l'ultima riga lancia Cannot read properties of undefined (reading 'city'). Inoltre any si propaga. user.name è any, quindi ogni valore calcolato da lì è any, e un solo valore non tipizzato può disattivare i controlli lontano dal punto in cui è entrato.
unknown ti obbliga a controllare prima
Con unknown, il compilatore rifiuta ogni operazione finché il codice non restringe il valore. Per restringere si usano normali controlli JavaScript, e dentro ogni ramo il valore ha il tipo verificato.
Il controllo value === null deve venire prima di quello sull'oggetto perché typeof null è "object". Dopo "id" in value, TypeScript sa che l'oggetto ha una proprietà id, tipizzata unknown, perché nulla dice cosa contiene. String() la trasforma esplicitamente in testo; per usarla come numero la controlleresti prima con typeof.
Puoi anche saltare il controllo con un'asserzione, value as string, e il compilatore la accetta. È una promessa senza alcun controllo a runtime dietro, quindi preferisci un controllo vero; vedi le type assertion.
Validare JSON con unknown
JSON.parse è dichiarato con ritorno any, quindi il suo risultato disattiva i controlli in silenzio. Annota il risultato come unknown e scrivi una type guard che verifichi la forma prima che il resto del codice se ne fidi.
Il primo input stampa dark at 14px, il secondo viene rifiutato perché manca fontSize. Con any, il secondo input sarebbe passato come Settings con fontSize undefined. Per schemi grandi, una libreria di validazione fa lo stesso lavoro e ricava il tipo al posto tuo.
noImplicitAny
any non arriva solo da chi lo scrive. Anche un parametro senza annotazione e senza contesto da cui dedurre il tipo sarebbe any. L'opzione noImplicitAny, attivata da strict, lo segnala come errore:
index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.
La soluzione è un'annotazione, function double(x: number). Anche scrivere esplicitamente x: any compila, ed è proprio questo il punto: any resta visibile nel codice e si può cercare e revisionare.
Dove any si infila ancora
Anche con strict, queste fonti producono any senza che la parola compaia nel tuo codice:
| Fonte | Cosa ottieni | Cosa fare |
|---|---|---|
JSON.parse(text) | any | annotalo come unknown, poi validalo |
response.json() con i tipi del browser (DOM) | Promise<any> | come per JSON.parse (i tipi del fetch di Node restituiscono già Promise<unknown>) |
| Un pacchetto senza definizioni di tipo | any per i suoi import (con un errore se non aggiungi una dichiarazione) | installa @types/... o scrivi un .d.ts |
value as any | any | usa una type guard o un'asserzione precisa |
catch (e) con useUnknownInCatchVariables disattivato | any | strict lo rende unknown; lascialo così |
La variabile del catch è unknown con strict perché si può lanciare qualsiasi cosa, non solo oggetti Error. Restringila con e instanceof Error prima di leggere e.message.
Quando any è accettabile
any non è vietato, ma ognuno è un punto che il compilatore non protegge più. Usi ragionevoli:
- Migrare un codebase JavaScript, dove
anysegna ciò che non è ancora tipizzato. - Codice che il sistema di tipi non esprime bene, tenuto piccolo e dietro la firma di una funzione tipizzata.
- Codice di test che passa di proposito dati sbagliati.
Per tutto il resto, unknown copre lo stesso caso "non conosco questo tipo" mantenendo i controlli. Molti team lo impongono con la regola di lint @typescript-eslint/no-explicit-any. Un tipo correlato, Record<string, unknown>, è la scelta abituale per "un oggetto qualsiasi con valori sconosciuti".
Domande frequenti
Che differenza c'è tra any e unknown in TypeScript?
Entrambi accettano qualsiasi valore. Con any puoi fare qualsiasi cosa con il valore (leggere proprietà, chiamarlo, assegnarlo a un number) e il compilatore non controlla nulla. Con unknown non puoi fare quasi nulla finché non lo restringi con un controllo come typeof x === "string". unknown lascia attivo il controllo dei tipi, ed è per questo che è la scelta più sicura.
Quando usare unknown invece di any?
Ogni volta che il tipo di un valore non è noto in compilazione: JSON analizzato, dati da una risposta di rete, un errore catturato, l'input di una funzione di validazione. Tipizzalo come unknown e restringilo. Ricorri ad any solo per lavori di migrazione temporanei o per codice che il sistema di tipi non riesce a descrivere.
Cosa significa "Object is of type 'unknown'"?
Gli errori TS18046 ('x' is of type 'unknown') e TS2571 (Object is of type 'unknown', usato quando il valore non è un semplice nome, come load().id) indicano che hai usato un valore unknown come se avesse un tipo preciso, per esempio leggendo una proprietà o chiamando un metodo. Controlla prima il tipo (typeof, instanceof, Array.isArray, in o una funzione type guard) e usa il valore dentro il ramo ristretto.
Perché JSON.parse restituisce any?
La sua dichiarazione nella libreria standard dice parse(text: string, ...): any, perché il compilatore non può sapere cosa contiene una stringa. Il risultato disattiva in silenzio il controllo per tutto ciò che tocca. Annotalo invece: const data: unknown = JSON.parse(text), poi validalo prima di usarlo.
Che cos'è noImplicitAny?
Un'opzione del compilatore, parte di strict, che segnala un errore quando una dichiarazione riceverebbe in silenzio il tipo any perché non ha annotazioni e niente da cui dedurlo: TS7006 per un parametro, TS7005 o TS7034 per una variabile il cui tipo non si può ricavare. Impedisce che any compaia senza che nessuno lo scriva. Un any esplicito resta consentito.