In TypeScript typeof significa due cose. In un'espressione è l'operatore JavaScript: viene eseguito, restituisce una stringa come "string" o "number", e TypeScript restringe la variabile quando confronti quella stringa. In una posizione di tipo è una type query: typeof config dà il tipo statico della variabile config.
L'ultima riga mostra la differenza: typeof custom in un console.log è l'operatore a runtime e stampa object, mentre typeof defaults dopo type Options = è la query in fase di compilazione e non arriva mai al JavaScript.
I due significati a colpo d'occhio
typeof a runtime (JavaScript) | typeof come type query (TypeScript) | |
|---|---|---|
| Dove compare | In qualsiasi espressione: if, return, console.log(...) | In posizioni di tipo: dopo :, in type X = ..., dentro <...> |
| Esempio | typeof x === "string" | let y: typeof x; |
| Valutato | Quando il codice viene eseguito | Dal compilatore, poi eliminato |
| Risultato | Una di otto stringhe | Un tipo |
| Serve per | Controllare un valore e restringerne il tipo | Ricavare un tipo da un valore |
TypeScript li distingue in base alla posizione, quindi la stessa parola chiave non significa mai entrambe le cose insieme.
I risultati di typeof a runtime
L'operatore a runtime restituisce una di otto stringhe. Esegui questo codice per vedere cosa danno i valori più comuni:
| Valore | Risultato di typeof | Nota |
|---|---|---|
"hi", template string | "string" | |
42, 3.14, NaN, Infinity | "number" | NaN è un numero; controllalo con Number.isNaN |
10n | "bigint" | |
true, false | "boolean" | |
undefined | "undefined" | |
Symbol("id") | "symbol" | |
| funzioni, arrow function, classi | "function" | una classe è una funzione costruttore |
null | "object" | un bug storico di JavaScript |
{}, [], new Date(), new Map() | "object" | gli array sono oggetti: usa Array.isArray |
new String("x"), new Number(1) | "object" | oggetti wrapper, evitali |
In JavaScript puro, typeof su una variabile mai dichiarata restituisce "undefined" invece di lanciare un errore. In TypeScript quel codice non compila: Cannot find name 'notDeclared'. (TS2304).
Narrowing con typeof
Un confronto con typeof in un if, uno switch, un operatore ternario o dopo && restringe la variabile in ogni ramo. È il modo standard per gestire una union di primitivi, e il primo controllo da usare su unknown.
typeof x !== "string" restringe nell'altra direzione, a tutto tranne string. E il compilatore conosce le otto stringhe possibili, quindi un errore di battitura nel confronto è un errore di compilazione, non un controllo che risulta sempre falso in silenzio:
Il compilatore stampa index.ts(3,7): error TS2367: This comparison appears to be unintentional because the types '"bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined"' and '"strnig"' have no overlap. In JavaScript puro l'errore di battitura manderebbe in silenzio ogni stringa nel percorso number. Correggi l'ortografia e stampa text.
La trappola di typeof null
Poiché typeof null è "object", TypeScript restringe typeof x === "object" a object | null, non a object. Aggiungi un controllo su null, altrimenti leggere una proprietà è un errore (TS18047 'x' is possibly 'null'.).
typeof non sa distinguere array, date, map e oggetti semplici: sono tutti "object". Usa Array.isArray(x) per gli array e x instanceof Date per le istanze di classi. Né typeof né altro possono controllare a runtime se un valore corrisponde a un'interface, perché le interface vengono eliminate; per questo, scrivi una funzione type guard che controlla le proprietà.
typeof nelle posizioni di tipo
In un tipo, typeof someVariable copia il tipo che TypeScript ha dedotto (o che hai dichiarato) per quella variabile. Ti evita di scrivere a mano il tipo di un valore che esiste già, e mantiene i due allineati.
const theme = {
primary: "#3178c6",
spacing: 8,
dark: false,
};
type Theme = typeof theme;
// { primary: string; spacing: number; dark: boolean }
function withSpacing(t: Theme, factor: number): Theme {
return { ...t, spacing: t.spacing * factor };
}
console.log(withSpacing(theme, 2)); // { primary: '#3178c6', spacing: 16, dark: false }
let userName = "Ada";
const fixed = "Ada";
type A = typeof userName; // string
type B = typeof fixed; // "Ada" (a const keeps its literal type)
Il risultato dipende da come è stata dichiarata la variabile: una let o una proprietà di un oggetto viene allargata (string, number), un primitivo const mantiene il suo tipo letterale. Aggiungi as const a un oggetto o a un array e typeof dà tipi letterali readonly a ogni livello. typeof accetta anche l'accesso alle proprietà, typeof theme.spacing è number, ma non espressioni arbitrarie: type T = typeof getTheme(); è un errore di sintassi (';' expected., TS1005). Per il tipo restituito da una chiamata, usa ReturnType<typeof getTheme>.
keyof typeof: le chiavi di un oggetto come tipo
keyof typeof obj è la combinazione più comune. typeof trasforma l'oggetto in un tipo, keyof ne prende le chiavi, e ottieni una union dei nomi delle proprietà che resta corretta quando l'oggetto cambia.
Lo stesso trucco su un array costante dà una union dei suoi valori: con const roles = ["admin", "editor"] as const, il tipo (typeof roles)[number] è "admin" | "editor". Senza as const sarebbe solo string. La pagina keyof tratta keyof da solo.
ReturnType<typeof fn> e typeof sulle classi
Utility type come ReturnType e Parameters accettano un tipo funzione. Il nome di una funzione è un valore, quindi passalo prima attraverso typeof:
Scrivere ReturnType<createUser> fallisce con 'createUser' refers to a value, but is being used as a type here. Did you mean 'typeof createUser'? (TS2749), ed è il compilatore che chiede proprio questo.
Le classi sono l'unico caso in cui un nome è sia un valore sia un tipo. User come tipo indica un'istanza della classe; typeof User indica la classe stessa, il costruttore, compresi i suoi membri statici:
class Point {
static origin = new Point(0, 0);
constructor(public x: number, public y: number) {}
}
const p: Point = new Point(1, 2); // an instance
const Ctor: typeof Point = Point; // the class (constructor + statics)
const q = new Ctor(3, 4); // q: Point
type Instance = InstanceType<typeof Point>; // Point
typeof, instanceof e type guard a confronto
| Controllo | Funziona su | Esempio |
|---|---|---|
typeof | primitivi, funzioni, "è un oggetto?" | typeof x === "number" |
Array.isArray | array | Array.isArray(x) |
instanceof | istanze di classi (Date, Error, le tue classi) | x instanceof Date |
in | union di oggetti, per proprietà | "email" in x |
| Funzione type guard | interface, type alias, qualsiasi altra cosa | isUser(x) con x is User |
Scegli lo strumento più specifico che funziona. Per le istanze di classi vedi instanceof; per le forme descritte da un'interface, una funzione type guard è l'unico controllo a runtime disponibile.
Domande frequenti
Cosa fa typeof in TypeScript?
Due cose diverse a seconda di dove compare. In un'espressione, typeof x è l'operatore JavaScript: viene eseguito e restituisce una stringa come "string" o "object", e TypeScript restringe x quando confronti quella stringa. In un'annotazione di tipo, typeof x è una type query: la valuta il compilatore, dà il tipo statico della variabile x e scompare dal JavaScript generato.
Cos'è keyof typeof in TypeScript?
keyof typeof obj dà la union dei nomi delle proprietà di un oggetto come tipi letterali stringa. typeof obj trasforma il valore oggetto nel suo tipo, e keyof prende le chiavi di quel tipo. Per const colors = { red: "#f00", blue: "#00f" }, keyof typeof colors è "red" | "blue".
Perché typeof null restituisce "object"?
È un bug della prima versione di JavaScript che non si può più correggere senza rompere il web. TypeScript ne tiene conto: dopo typeof x === "object", x viene ristretto a object | null, quindi devi controllare anche x !== null prima di leggere le proprietà.
Come verifico se un valore è un array in TypeScript?
Usa Array.isArray(value), che restringe a un tipo array. typeof non può farlo: typeof [] è "object", come per gli oggetti semplici e per null.
Che differenza c'è tra typeof e instanceof in TypeScript?
typeof controlla la categoria primitiva di un valore ("string", "number", "function", "object"...). instanceof controlla se un oggetto è stato creato da una certa classe o costruttore, come Date o una tua class User. Usa typeof per i primitivi e instanceof per le istanze di classi; nessuno dei due può controllare un'interface o un type alias, che a runtime non esistono.