readonly markiert eine Eigenschaft, die einmal gesetzt werden kann, beim Erzeugen des Objekts, und nie neu zugewiesen wird. Readonly<T> wendet das auf jede Eigenschaft eines Typs an, und readonly T[] tut dasselbe für Arrays:
Die letzte Zeile zeigt das Wichtigste an readonly: Der Compiler prüft es, zur Laufzeit wird es nicht durchgesetzt. Die Zuweisung war ein Compilerfehler (hier mit @ts-expect-error unterdrückt), und trotzdem hat das erzeugte JavaScript sie ausgeführt. Ohne die Unterdrückung würde die Datei nicht kompilieren, und genau dort erledigt readonly seine Aufgabe.
readonly-Eigenschaften
Setze readonly vor einen Eigenschaftsnamen in einem Interface, einem Typliteral oder einer Klasse. Die Eigenschaft kann initialisiert, aber nicht neu zugewiesen werden:
In einer Klasse kann ein readonly-Feld in seiner Deklaration oder im Konstruktor zugewiesen werden, sonst nirgends. Die kürzeste Form ist eine Parameter Property, constructor(readonly id: string) {}, die das Feld in einem Schritt deklariert und zuweist. Die Seite zu Klassen behandelt Felder und Konstruktoren allgemein.
Readonly<T>: jede Eigenschaft auf einmal
Readonly<T> ist ein Utility Type, der jede Eigenschaft von T als readonly markiert. Das ist nützlich für Werte, die du weitergibst, die aber nicht verändert werden sollen, etwa den Zustand einer Anwendung:
Die Signatur der Funktion sagt dem Leser, dass addItem einen neuen Zustand zurückgibt, statt den alten zu ändern, und der Compiler hält die Funktion daran fest. Readonly<T> ist als der Mapped Type { readonly [P in keyof T]: T[P] } definiert.
Readonly-Arrays: readonly T[] und ReadonlyArray<T>
readonly number[] und ReadonlyArray<number> sind derselbe Typ. Sie entfernen jede verändernde Methode (push, pop, shift, splice, sort, reverse, fill...) und verbieten die Zuweisung per Index. Die nicht verändernden Methoden bleiben und geben gewöhnliche Arrays zurück:
Wenn du readonly T[] als Parameter nimmst, versprichst du Aufrufern, ihr Array nicht zu verändern. In der anderen Richtung bleiben viele hängen: Ein readonly-Array lässt sich nicht an eine Funktion übergeben, die ein einfaches T[] nimmt, weil diese Funktion es verändern könnte.
index.ts(7,17): error TS4104: The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
Die Lösung ist, sum so zu ändern, dass es readonly number[] akzeptiert, weil es nichts verändert. Funktionen, die ein Array nur lesen, sollten immer den readonly-Typ nehmen; dann akzeptieren sie beide Arten. Gehört dir die Funktion nicht, übergib eine Kopie: sum([...prices]).
ReadonlyMap und ReadonlySet
Auch Maps und Sets haben readonly-Versionen. ReadonlyMap<K, V> hat get, has, size, forEach und die Iteratoren, aber kein set, delete oder clear; ReadonlySet<T> hat kein add, delete oder clear:
Eine Klasse hält oft eine private, veränderbare Map und gibt sie über einen Getter vom Typ ReadonlyMap nach außen, sodass äußerer Code die Daten lesen, aber über diese Referenz nicht ändern kann.
readonly ist flach
readonly und Readonly<T> schützen nur die Eigenschaft selbst, nicht das Objekt oder Array, auf das sie zeigt:
DeepReadonly<T> wendet sich selbst auf jeden verschachtelten Objekttyp an, und weil ein Mapped Type über einen Array-Typ ein readonly-Array erzeugt, wird members zu readonly string[]. Es bleibt ein Versprechen auf Typebene, kein Schutz zur Laufzeit.
Nur beim Kompilieren: Änderungen über eine andere Referenz
Ein readonly-Typ steuert, was eine Referenz tun darf. Eine andere Referenz auf dasselbe Objekt, ohne readonly typisiert, kann es ändern, und TypeScript erlaubt sogar, einen readonly-Typ einem veränderbaren zuzuweisen:
Die Zuweisung mutable = settings kompiliert, weil TypeScript readonly-Eigenschaften nicht berücksichtigt, wenn es prüft, ob zwei Objekttypen kompatibel sind; das TypeScript-Handbuch sagt das ausdrücklich und weist darauf hin, dass sich readonly-Eigenschaften deshalb über Aliasing ändern können. Bei readonly-Arrays ist das anders: Der Fehler TS4104 oben ist genau diese Prüfung. Object.freeze verhindert Änderungen wirklich zur Laufzeit: Der erzeugte Code läuft im Strict Mode, in dem das Schreiben auf eine eingefrorene Eigenschaft einen TypeError wirft. Wie readonly ist Object.freeze flach.
readonly, const, as const und Object.freeze
as const bei einem Literal macht jede Eigenschaft auf jeder Ebene readonly und behält Literaltypen, und das ist oft der einfachste Weg zu einem tief schreibgeschützten Wert:
const theme = { mode: "dark", sizes: [12, 14] } as const;
// { readonly mode: "dark"; readonly sizes: readonly [12, 14] }
| Gilt für | Tief? | Wirkung zur Laufzeit | Beispiel | |
|---|---|---|---|---|
const | eine Variablenbindung | nein | die Variable lässt sich nicht neu zuweisen | const user = {...} |
readonly | eine Eigenschaft oder einen Array-Typ | nein | keine | readonly id: string |
Readonly<T> | jede Eigenschaft eines Typs | nein | keine | Readonly<State> |
as const | einen Literal-Ausdruck | ja | keine | { ... } as const |
Object.freeze | einen Objektwert | nein | Schreibzugriffe scheitern (werfen im Strict Mode) | Object.freeze(obj) |
const und readonly beantworten verschiedene Fragen: const verhindert, dass der Name woandershin zeigt, readonly verhindert, dass sich eine Eigenschaft ändert. Die Eigenschaften eines const-Objekts lassen sich weiterhin neu zuweisen, solange sie nicht readonly sind.
Häufig gestellte Fragen
Was macht readonly in TypeScript?
readonly markiert eine Eigenschaft, die beim Erzeugen des Objekts (oder im Konstruktor einer Klasse) gesetzt werden kann, danach aber nicht mehr neu zugewiesen werden darf. Eine spätere Zuweisung ist ein Compilerfehler, TS2540. Es ist nur eine Typprüfung: Das erzeugte JavaScript enthält keinen Schutz.
Was ist der Unterschied zwischen readonly und const in TypeScript?
const betrifft eine Variable: Der Name kann nicht auf einen anderen Wert zeigen, aber das Objekt, das er enthält, lässt sich weiterhin ändern. readonly betrifft eine Eigenschaft: Diese Eigenschaft kann nicht neu zugewiesen werden. const user = { name: "Ada" } erlaubt weiterhin user.name = "x"; eine Eigenschaft readonly name nicht.
Wie mache ich ein Array in TypeScript readonly?
Annotiere es als readonly T[] oder ReadonlyArray<T> (derselbe Typ). Verändernde Methoden wie push, pop, sort und splice verschwinden aus dem Typ, und eine Zuweisung per Index ist ein Fehler. Nicht verändernde Methoden wie map, filter und slice funktionieren weiter und geben gewöhnliche Arrays zurück.
Wirkt Readonly in TypeScript in die Tiefe?
Nein. Readonly<T> und readonly schützen nur die Eigenschaften der obersten Ebene; verschachtelte Objekte und Arrays darin lassen sich weiterhin ändern. Nimm as const bei einem Literal oder schreibe einen rekursiven Typ DeepReadonly<T>, um auf Typebene in die Tiefe zu schützen.
Verhindert readonly Änderungen zur Laufzeit?
Nein. Typen werden entfernt, also ist eine readonly-Eigenschaft zur Laufzeit eine gewöhnliche Eigenschaft, und Code mit einer veränderbaren Referenz auf dasselbe Objekt (oder einfaches JavaScript) kann sie weiterhin ändern. Nimm Object.freeze, wenn du Schutz zur Laufzeit brauchst; TypeScript typisiert das Ergebnis als Readonly<T>.