readonly segna una proprietà che si può impostare una sola volta, quando l'oggetto viene creato, e che non viene mai riassegnata. Readonly<T> lo applica a tutte le proprietà di un tipo, e readonly T[] fa lo stesso per gli array:
L'ultima riga mostra il fatto più importante su readonly: viene controllato dal compilatore, non imposto in fase di esecuzione. L'assegnazione era un errore di compilazione (qui soppresso con @ts-expect-error), eppure il JavaScript generato l'ha eseguita lo stesso. Senza la soppressione, il file non compilerebbe, ed è lì che readonly fa il suo lavoro.
Proprietà readonly
Metti readonly prima del nome di una proprietà in un'interfaccia, in un tipo letterale o in una classe. La proprietà si può inizializzare ma non riassegnare:
In una classe, un campo readonly si può assegnare nella sua dichiarazione o nel costruttore, e da nessun'altra parte. La forma più breve è una parameter property, constructor(readonly id: string) {}, che dichiara e assegna il campo in un solo passo. La pagina sulle classi tratta campi e costruttori in generale.
Readonly<T>: tutte le proprietà in un colpo solo
Readonly<T> è un utility type che segna tutte le proprietà di T come readonly. È utile per valori che passi in giro ma che non dovrebbero cambiare, come lo stato dell'applicazione:
La firma della funzione dice a chi legge che addItem restituisce un nuovo stato invece di modificare quello vecchio, e il compilatore obbliga la funzione a rispettarlo. Readonly<T> è definito come il mapped type { readonly [P in keyof T]: T[P] }.
Array readonly: readonly T[] e ReadonlyArray<T>
readonly number[] e ReadonlyArray<number> sono lo stesso tipo. Tolgono tutti i metodi che modificano (push, pop, shift, splice, sort, reverse, fill...) e vietano l'assegnazione tramite indice. I metodi che non modificano restano e restituiscono array normali:
Accettare readonly T[] come parametro è una promessa a chi chiama che non modificherai il suo array. È nella direzione opposta che ci si blocca: un array readonly non si può passare a una funzione che accetta un semplice T[], perché quella funzione potrebbe modificarlo.
index.ts(7,17): error TS4104: The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
La soluzione è cambiare sum perché accetti readonly number[], dato che non modifica nulla. Le funzioni che si limitano a leggere un array dovrebbero sempre accettare il tipo readonly; così accettano entrambi i tipi di array. Se la funzione non è tua, passa una copia: sum([...prices]).
ReadonlyMap e ReadonlySet
Anche le Map e i Set hanno versioni readonly. ReadonlyMap<K, V> ha get, has, size, forEach e gli iteratori ma non set, delete o clear; ReadonlySet<T> non ha add, delete o clear:
Spesso una classe tiene una Map privata modificabile e la espone tramite un getter tipizzato ReadonlyMap, così il codice esterno può leggere i dati ma non modificarli tramite quel riferimento.
readonly è superficiale
readonly e Readonly<T> proteggono solo la proprietà in sé, non l'oggetto o l'array a cui punta:
DeepReadonly<T> si applica a ogni tipo oggetto annidato, e dato che un mapped type su un tipo array produce un array readonly, members diventa readonly string[]. Resta comunque una promessa a livello di tipi, non una protezione in fase di esecuzione.
Solo in compilazione: modifiche tramite un altro riferimento
Un tipo readonly controlla cosa può fare un riferimento. Un altro riferimento allo stesso oggetto, tipizzato senza readonly, può modificarlo, e TypeScript permette persino di assegnare un tipo readonly a uno modificabile:
L'assegnazione mutable = settings compila perché TypeScript non tiene conto delle proprietà readonly quando controlla se due tipi oggetto sono compatibili; il manuale di TypeScript lo dice esplicitamente, e osserva che le proprietà readonly possono quindi cambiare tramite aliasing. Gli array readonly sono diversi: l'errore TS4104 visto sopra è esattamente quel controllo. Object.freeze impedisce davvero le modifiche in fase di esecuzione: il codice generato gira in strict mode, dove scrivere su una proprietà congelata lancia un TypeError. Come readonly, anche Object.freeze è superficiale.
readonly, const, as const e Object.freeze
as const su un letterale rende readonly ogni proprietà a ogni livello e mantiene i tipi letterali, ed è spesso il modo più facile per ottenere un valore readonly in profondità:
const theme = { mode: "dark", sizes: [12, 14] } as const;
// { readonly mode: "dark"; readonly sizes: readonly [12, 14] }
| Si applica a | Profondo? | Effetto in esecuzione | Esempio | |
|---|---|---|---|---|
const | un binding di variabile | no | la variabile non si può riassegnare | const user = {...} |
readonly | una proprietà o un tipo array | no | nessuno | readonly id: string |
Readonly<T> | tutte le proprietà di un tipo | no | nessuno | Readonly<State> |
as const | un'espressione letterale | sì | nessuno | { ... } as const |
Object.freeze | un valore oggetto | no | le scritture falliscono (lanciano in strict mode) | Object.freeze(obj) |
const e readonly rispondono a domande diverse: const impedisce al nome di puntare altrove, readonly impedisce a una proprietà di cambiare. Le proprietà di un oggetto const si possono ancora riassegnare, a meno che non siano readonly.
Domande frequenti
Cosa fa readonly in TypeScript?
readonly segna una proprietà che si può impostare quando l'oggetto viene creato (o nel costruttore di una classe) ma non riassegnare in seguito. Assegnarle un valore dopo è un errore di compilazione, TS2540. È solo un controllo dei tipi: il JavaScript generato non contiene alcuna protezione.
Che differenza c'è tra readonly e const in TypeScript?
const riguarda una variabile: il nome non può puntare a un altro valore, ma l'oggetto che contiene si può ancora modificare. readonly riguarda una proprietà: quella proprietà non si può riassegnare. const user = { name: "Ada" } permette ancora user.name = "x"; una proprietà readonly name no.
Come rendo readonly un array in TypeScript?
Annotalo come readonly T[] o ReadonlyArray<T> (è lo stesso tipo). I metodi che modificano, come push, pop, sort e splice, spariscono dal tipo, e l'assegnazione tramite indice è un errore. I metodi che non modificano, come map, filter e slice, continuano a funzionare e restituiscono array normali.
Readonly è profondo in TypeScript?
No. Readonly<T> e readonly proteggono solo le proprietà di primo livello; gli oggetti e gli array annidati si possono ancora modificare. Usa as const su un letterale, oppure scrivi un tipo ricorsivo DeepReadonly<T>, per una protezione profonda a livello di tipi.
readonly impedisce le modifiche in fase di esecuzione?
No. I tipi vengono cancellati, quindi in esecuzione una proprietà readonly è una proprietà normale, e il codice con un riferimento modificabile allo stesso oggetto (o JavaScript puro) può ancora cambiarla. Usa Object.freeze quando ti serve una protezione in esecuzione; TypeScript tipizza il suo risultato come Readonly<T>.