Menu

readonly w TypeScript: właściwości, Readonly<T> i tablice

Modyfikator readonly i typ narzędziowy Readonly<T> blokują ponowne przypisywanie właściwości. Zobacz właściwości readonly i pola klas, Readonly<T>, tablice tylko do odczytu (readonly T[] i ReadonlyArray), ReadonlyMap i ReadonlySet, dlaczego readonly jest płytkie i działa tylko podczas kompilacji oraz jak wypada na tle Object.freeze i as const.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

readonly oznacza właściwość, którą można ustawić raz, przy tworzeniu obiektu, i nigdy więcej nie przypisać. Readonly<T> stosuje to do każdej właściwości typu, a readonly T[] robi to samo dla tablic:

Ostatnia linia pokazuje najważniejszy fakt o readonly: sprawdza go kompilator, ale nie jest egzekwowany w czasie działania. Przypisanie było błędem kompilacji (tu wyciszonym przez @ts-expect-error), a mimo to wygenerowany JavaScript je wykonał. Bez wyciszenia plik by się nie skompilował i właśnie tam readonly spełnia swoje zadanie.

Właściwości readonly

Postaw readonly przed nazwą właściwości w interfejsie, literale typu lub klasie. Właściwość można zainicjalizować, ale nie ponownie przypisać:

W klasie pole readonly można przypisać w jego deklaracji albo w konstruktorze i nigdzie indziej. Najkrótsza forma to parameter property, constructor(readonly id: string) {}, która deklaruje i przypisuje pole w jednym kroku. Strona o klasach omawia pola i konstruktory ogólnie.

Readonly<T>: wszystkie właściwości naraz

Readonly<T> to typ narzędziowy, który oznacza każdą właściwość T jako readonly. Przydaje się dla wartości, które przekazujesz dalej, ale których nie należy zmieniać, na przykład stanu aplikacji:

Sygnatura funkcji mówi czytelnikowi, że addItem zwraca nowy stan zamiast zmieniać stary, a kompilator pilnuje, żeby funkcja się tego trzymała. Readonly<T> jest zdefiniowany jako typ mapowany { readonly [P in keyof T]: T[P] }.

Tablice tylko do odczytu: readonly T[] i ReadonlyArray<T>

readonly number[] i ReadonlyArray<number> to ten sam typ. Usuwają każdą metodę mutującą (push, pop, shift, splice, sort, reverse, fill...) i zabraniają przypisania przez indeks. Metody niemutujące zostają i zwracają zwykłe tablice:

Przyjmowanie readonly T[] jako parametru to obietnica dla wywołujących, że nie zmodyfikujesz ich tablicy. W drugą stronę ludzie często utykają: tablicy readonly nie można przekazać do funkcji przyjmującej zwykłe T[], bo ta funkcja mogłaby ją zmienić.

index.ts(7,17): error TS4104: The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.

Rozwiązanie to zmienić sum tak, by przyjmowała readonly number[], bo niczego nie mutuje. Funkcje, które tylko czytają tablicę, powinny zawsze przyjmować typ readonly; wtedy akceptują oba rodzaje. Jeśli funkcja nie jest twoja, przekaż kopię: sum([...prices]).

ReadonlyMap i ReadonlySet

Mapy i zbiory też mają wersje tylko do odczytu. ReadonlyMap<K, V> ma get, has, size, forEach i iteratory, ale nie ma set, delete ani clear; ReadonlySet<T> nie ma add, delete ani clear:

Klasa często trzyma prywatną mutowalną Map i udostępnia ją przez getter o typie ReadonlyMap, więc kod z zewnątrz może czytać dane, ale nie może ich zmienić przez tę referencję.

readonly jest płytkie

readonly i Readonly<T> chronią tylko samą właściwość, a nie obiekt lub tablicę, na którą wskazuje:

DeepReadonly<T> stosuje się do każdego zagnieżdżonego typu obiektowego, a ponieważ typ mapowany na typie tablicy daje tablicę readonly, members staje się readonly string[]. To nadal obietnica na poziomie typów, a nie ochrona w czasie działania.

Tylko podczas kompilacji: mutacja przez inną referencję

Typ readonly kontroluje, co może zrobić jedna referencja. Inna referencja do tego samego obiektu, otypowana bez readonly, może go zmienić, a TypeScript pozwala nawet przypisać typ readonly do mutowalnego:

Przypisanie mutable = settings się kompiluje, bo TypeScript nie bierze pod uwagę właściwości readonly, gdy sprawdza, czy dwa typy obiektowe są zgodne; handbook TypeScriptu mówi to wprost i zauważa, że właściwości readonly mogą się przez to zmieniać przez aliasy. Z tablicami readonly jest inaczej: błąd TS4104 wyżej to właśnie takie sprawdzenie. Object.freeze naprawdę blokuje zmiany w czasie działania: wygenerowany kod działa w trybie strict, w którym zapis do zamrożonej właściwości rzuca TypeError. Tak jak readonly, Object.freeze jest płytkie.

readonly, const, as const i Object.freeze

as const na literale czyni każdą właściwość readonly na każdym poziomie i zachowuje typy literałowe, co często jest najprostszym sposobem na głęboko readonly wartość:

const theme = { mode: "dark", sizes: [12, 14] } as const;
// { readonly mode: "dark"; readonly sizes: readonly [12, 14] }
DotyczyGłęboko?Efekt w czasie działaniaPrzykład
constwiązania zmiennejniezmiennej nie można ponownie przypisaćconst user = {...}
readonlyjednej właściwości lub typu tablicyniebrakreadonly id: string
Readonly<T>każdej właściwości typuniebrakReadonly<State>
as constwyrażenia literałowegotakbrak{ ... } as const
Object.freezewartości obiektuniezapisy się nie udają (rzucają w trybie strict)Object.freeze(obj)

const i readonly odpowiadają na różne pytania: const nie pozwala nazwie wskazywać gdzie indziej, readonly nie pozwala zmienić właściwości. Właściwości obiektu const nadal można ponownie przypisać, chyba że są readonly.

Najczęściej zadawane pytania

Co robi readonly w TypeScript?

readonly oznacza właściwość, którą można ustawić przy tworzeniu obiektu (lub w konstruktorze klasy), ale nie można jej potem ponownie przypisać. Późniejsze przypisanie to błąd kompilacji, TS2540. To tylko sprawdzenie typów: wygenerowany JavaScript nie zawiera żadnej ochrony.

Czym różni się readonly od const w TypeScript?

const dotyczy zmiennej: nazwy nie można skierować na inną wartość, ale obiekt, który przechowuje, nadal można zmieniać. readonly dotyczy właściwości: tej właściwości nie można ponownie przypisać. const user = { name: "Ada" } nadal pozwala na user.name = "x"; właściwość readonly name nie pozwala.

Jak zrobić tablicę tylko do odczytu w TypeScript?

Oznacz ją jako readonly T[] albo ReadonlyArray<T> (to ten sam typ). Metody mutujące, takie jak push, pop, sort i splice, znikają z typu, a przypisanie przez indeks to błąd. Metody niemutujące, jak map, filter i slice, nadal działają i zwracają zwykłe tablice.

Czy Readonly w TypeScript działa głęboko?

Nie. Readonly<T> i readonly chronią tylko właściwości najwyższego poziomu; zagnieżdżone obiekty i tablice w środku nadal można zmieniać. Dla głębokiej ochrony na poziomie typów użyj as const na literale albo napisz rekurencyjny typ DeepReadonly<T>.

Czy readonly zapobiega zmianom w czasie działania?

Nie. Typy są usuwane, więc właściwość readonly jest w czasie działania zwykłą właściwością, a kod z mutowalną referencją do tego samego obiektu (albo zwykły JavaScript) nadal może ją zmienić. Użyj Object.freeze, gdy potrzebujesz ochrony w czasie działania; TypeScript typuje jego wynik jako Readonly<T>.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ