Un literal type è un tipo con un solo valore: "up" è un tipo il cui unico membro è la stringa "up", e 404 è un tipo il cui unico membro è il numero 404. Da soli non sono molto utili. Uniti in un'unione, ti danno una variabile che accetta un insieme fisso di valori e nient'altro.
Senza il commento @ts-expect-error, l'ultima chiamata è un errore in fase di compilazione (TS2345). Con il commento, il programma compila e la chiamata viene comunque eseguita e stampa moving north by 1: i literal types esistono solo per il compilatore, e a runtime il valore è una stringa normale.
Letterali stringa, numerici e booleani
Qualsiasi valore stringa, numerico, bigint o booleano si può scrivere come tipo. Il compilatore accetta poi solo quel valore esatto.
let method: "GET" = "GET";
let code: 404 = 404;
let ok: true = true;
let big: 10n = 10n;
type Port = 80 | 443 | 8080;
const port: Port = 443;
boolean stesso non è altro che l'unione true | false, ed è per questo che restringere un boolean con if (flag) lascia false nel ramo else.
| Literal type | Ammette | Tipo più ampio |
|---|---|---|
"GET" | solo la stringa "GET" | string |
404 | solo il numero 404 | number |
10n | solo il bigint 10 | bigint |
true | solo true | boolean |
Unioni di letterali
L'uso comune è un'unione che elenca tutti i valori ammessi. Dentro la funzione il compilatore restringe l'unione man mano che la controlli, quindi ogni ramo sa esattamente quale valore ha.
Un'unione di letterali stringa è l'alternativa abituale a un enum in TypeScript. Non costa niente a runtime, i valori sono stringhe normali che puoi stampare nei log e inviare in JSON, e un errore di battitura è un errore di compilazione. Trovi di più su pro e contro in enums.
Widening: let vs const
Quando TypeScript deduce un tipo da un letterale, guarda se il valore può cambiare. Una variabile const non può mai essere riassegnata, quindi mantiene il literal type. Una variabile let sì, quindi il suo tipo viene allargato al tipo generale.
Passa il mouse su ogni nome nell'editor per vedere il tipo dedotto. Se vuoi un let che contenga solo certi valori, annotalo: let mode: "light" | "dark" = "light".
Perché le proprietà degli oggetti vengono allargate
Le proprietà di un oggetto letterale sono modificabili, quindi vengono allargate anche loro, pure quando l'oggetto è memorizzato in una const. È il modo più comune di imbattersi per caso nei literal types:
Il compilatore segnala:
index.ts(7,15): error TS2345: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
req viene dedotto come { url: string; method: string } perché più avanti del codice potrebbe eseguire req.method = "DELETE". Ci sono tre soluzioni:
Una quarta opzione è satisfies, che controlla l'oggetto rispetto a un tipo mantenendo i literal types delle sue proprietà.
as const
as const è una const assertion. Mettila dopo un'espressione e il compilatore deduce il tipo più stretto possibile:
- i valori stringa, numerici e booleani mantengono i loro literal types
- le proprietà degli oggetti diventano
readonly - gli array letterali diventano tuple
readonlydi lunghezza fissa
L'asserzione vale solo in fase di compilazione. Il JavaScript emesso è lo stesso oggetto letterale senza as const, quindi niente impedisce ad altro codice di modificarlo a runtime. Se ti serve una garanzia a runtime, chiama anche Object.freeze.
Un tipo unione da un array as const
Un pattern frequente è tenere i valori ammessi in un solo array, su cui puoi iterare a runtime, e ricavarne il tipo unione. (typeof arr)[number] significa "il tipo di un elemento qualsiasi di arr".
Senza as const, ROLES sarebbe string[] e Role sarebbe una semplice string. Il cast a readonly string[] in isRole serve perché includes su una tupla di letterali accetta solo quei letterali, e lo scopo della funzione è testare una stringa che potrebbe non esserlo. Lo stesso pattern con un oggetto funziona per le mappe chiave/valore: const Status = { Active: "active", Banned: "banned" } as const, poi type Status = (typeof Status)[keyof typeof Status].
const type parameters
Una funzione generica di norma allarga i letterali che le passi. Da TypeScript 5.0 puoi marcare un parametro di tipo come const, e il compilatore dedurrà l'argomento come se avesse as const, senza chiedere a chi chiama di scriverlo.
È soprattutto uno strumento per chi scrive librerie: definizioni di route, builder e helper per schemi lo usano perché chi chiama ottenga tipi precisi da semplici letterali.
I significati di const
La parola chiave const compare in quattro posti diversi nel codice TypeScript:
| Sintassi | Tipo | Cosa fa |
|---|---|---|
const x = 1 | dichiarazione JavaScript | il binding non può essere riassegnato; un valore letterale mantiene il suo literal type |
expr as const | asserzione TypeScript | tipo più stretto: letterali, proprietà readonly, tuple readonly |
function f<const T>() | parametro di tipo TypeScript | deduce gli argomenti come se avessero as const |
const enum E {} | enum TypeScript | un enum i cui membri vengono sostituiti in linea in fase di compilazione |
Nessuno di questi congela un oggetto a runtime. const obj = { a: 1 } permette ancora obj.a = 2; è un errore solo riassegnare obj stesso.
Domande frequenti
Cos'è un literal type in TypeScript?
Un tipo che ammette un solo valore. "GET" è un tipo il cui unico valore è la stringa "GET", 404 è un tipo il cui unico valore è il numero 404, e true è un tipo il cui unico valore è true. Sono più utili combinati in unioni, come type Method = "GET" | "POST".
Cosa fa as const in TypeScript?
as const è una const assertion. Dice al compilatore di dedurre il tipo più stretto possibile per un'espressione: i valori stringa e numerici mantengono i loro literal types, le proprietà degli oggetti diventano readonly, e gli array letterali diventano tuple readonly. Cambia solo il tipo; a runtime il valore è lo stesso oggetto o array semplice, e non viene congelato.
Perché TypeScript deduce string invece del mio letterale?
Perché il valore è modificabile. let x = "a" e la proprietà in { method: "GET" } possono essere riassegnate più avanti, quindi TypeScript le allarga a string. Una variabile const mantiene il literal type "a". Per mantenere i letterali dentro un oggetto, annotalo con il literal type, usa as const, oppure usa satisfies.
Qual è la differenza tra const e as const?
const è una dichiarazione JavaScript: la variabile non può essere riassegnata, ma l'oggetto a cui punta può ancora essere modificato. as const è una type assertion di TypeScript: rende ogni proprietà del valore readonly e letterale nel sistema dei tipi. Nessuno dei due congela l'oggetto a runtime; per quello usa Object.freeze.
Come si ottiene un tipo unione da un array di stringhe?
Dichiara l'array con as const, poi indicizza il suo tipo con number: const roles = ["admin", "user"] as const; type Role = (typeof roles)[number]; dà "admin" | "user". Senza as const l'array è string[] e il risultato è solo string.