Wzorce głębokiej niezmienności
Osiągaj głęboko readonly struktury za pomocą typów rekurencyjnych
Wzorce głębokiej niezmienności to bezpłatna lekcja TypeScript Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej TypeScript Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs TypeScript Academy zawiera 4 lekcji w sumie.
Płytka a głęboka niezmienność
Modyfikator readonly działa płytko: chroni najwyższy poziom, ale nie zagnieżdżone obiekty. Głęboka niezmienność oznacza, że każdy poziom, aż do samego końca, jest tylko do odczytu. Osiągnięcie jej wymaga dodatkowej pracy.
interface State { readonly user: { name: string }; }
const s: State = { user: { name: 'Ada' } };
// s.user = {...}; // Error (shallow protection)
s.user.name = 'Grace'; // allowed! nested field is mutable
console.log(s.user.name);Dlaczego płytka ochrona nie wystarcza
W zarządzaniu stanem i współdzielonymi danymi płytka ochrona tylko do odczytu pozostawia zagnieżdżone pola dostępne do modyfikacji. Jedna przeoczona modyfikacja głęboko w strukturze może spowodować subtelne błędy, które trudno prześledzić.
interface Config { readonly db: { host: string; port: number }; }
const c: Config = { db: { host: 'localhost', port: 5432 } };
c.db.port = 9999; // mutates nested data despite readonly
console.log(c.db.port);Rekurencyjny typ DeepReadonly
Można zbudować typ mapowany, który rekurencyjnie stosuje readonly. DeepReadonly<T> przechodzi przez każdą właściwość i, jeśli jest ona obiektem, rekurencyjnie stosuje się do jej zawartości.
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
console.log('DeepReadonly defined');Stosowanie DeepReadonly
Opakowanie typu w DeepReadonly sprawia, że zagnieżdżone właściwości również stają się niezmienne. Teraz nie można ponownie przypisać nawet s.user.name — ochrona sięga aż do najgłębszego poziomu.
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
type State = DeepReadonly<{ user: { name: string } }>;
const s: State = { user: { name: 'Ada' } };
// s.user.name = 'x'; // Error now
console.log(s.user.name);Jak działa typ mapowany
Wyrażenie [K in keyof T] iteruje po każdym kluczu. Przedrostek readonly blokuje każdy z nich. Wyrażenie warunkowe T[K] extends object ? ... : T[K] decyduje, czy należy kontynuować rekurencję, czy zatrzymać się na typie prostym.
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
type Cfg = DeepReadonly<{ a: number; b: { c: string } }>;
const cfg: Cfg = { a: 1, b: { c: 'x' } };
console.log(cfg.a, cfg.b.c);Ochrona tylko w czasie kompilacji
DeepReadonly wymusza niezmienność w systemie typów, a nie w czasie działania. Obiekt JavaScriptu nadal jest technicznie modyfikowalny przez ścieżki bez typowania — ale kod korzystający z typów jest w pełni chroniony.
// Type-level immutability does not freeze the runtime object.
// For runtime guarantees, combine it with Object.freeze.
console.log('Types guard your code; freeze guards runtime');Zamrażanie zagnieżdżonych obiektów w czasie działania
Aby uzyskać rzeczywistą niezmienność w czasie działania, należy użyć Object.freeze. Zapobiega on dodawaniu, usuwaniu i zmienianiu właściwości — ale, podobnie jak readonly, domyślnie działa płytko.
const config = Object.freeze({ host: 'localhost', port: 8080 });
console.log(config.port);
// In strict mode, config.port = 1 throws; otherwise silently ignored.Pomocnicza funkcja głębokiego zamrażania
Aby głęboko zamrozić obiekt w czasie działania, należy rekurencyjnie przejść przez zagnieżdżone obiekty i zamrozić każdy z nich. Naturalnie uzupełnia to typowy dla systemu typów DeepReadonly, zapewniając niezmienność od początku do końca.
function deepFreeze<T>(obj: T): T {
Object.values(obj as any).forEach((v) => {
if (v && typeof v === 'object') deepFreeze(v);
});
return Object.freeze(obj);
}
const frozen = deepFreeze({ a: { b: 1 } });
console.log(frozen.a.b);Niezmienność dla bezpieczniejszego stanu
Głęboka niezmienność jest podstawą przewidywalnego stanu. Gdy danych nie można modyfikować w miejscu, aktualizacje muszą tworzyć nowe obiekty, dzięki czemu zmiany są jawne i łatwe do śledzenia.
const state = { count: 0, user: { name: 'Ada' } };
// Immutable update: create a new object instead of mutating
const next = { ...state, count: state.count + 1 };
console.log(state.count, next.count);Wzorzec niezmiennych aktualizacji
W przypadku danych tylko do odczytu nowe wersje tworzy się za pomocą operatora spread. Każda aktualizacja tworzy nowy obiekt, więc poprzednie stany pozostają niezmienione — jest to niezwykle przydatne przy cofaniu zmian, debugowaniu z podróżą w czasie i wykrywaniu zmian.
type State = { readonly items: readonly string[] };
const s: State = { items: ['a'] };
const updated: State = { items: [...s.items, 'b'] };
console.log(s.items, updated.items);Łączenie bezpieczeństwa typów i czasu działania
Najskuteczniejsze podejście wykorzystuje oba mechanizmy: DeepReadonly zapewnia gwarancje podczas kompilacji, a deepFreeze wymusza niezmienność w czasie działania. Razem sprawiają, że przypadkowa modyfikacja staje się niemal niemożliwa.
function freeze<T>(obj: T): T {
return Object.freeze(obj);
}
const settings = freeze({ theme: 'dark', version: 2 });
console.log(settings.theme, settings.version);Szybkie sprawdzenie
Proszę sprawdzić swoją wiedzę na temat głębokiej niezmienności.
Powtórzenie: głęboka niezmienność
Dowiedzieli się Państwo, że:
readonlydziała płytko; zagnieżdżone pola pozostają modyfikowalne.- Rekurencyjny typ mapowany
DeepReadonly<T>stosuje readonly na każdym poziomie. - Działa tylko podczas kompilacji; do wymuszania niezmienności w czasie działania należy użyć (głębokiego)
Object.freeze. - Niezmienność umożliwia bezpieczne i przewidywalne zarządzanie stanem za pomocą wzorców kopiowania przy aktualizacji.
To kończy ścieżkę TypeScript Academy poświęconą zaawansowanym typom i niezmienności.
type State = { readonly value: number };
const s: State = { value: 1 };
const next: State = { ...s, value: s.value + 1 };
console.log(s.value, next.value);Często zadawane pytania
Czy lekcja „Wzorce głębokiej niezmienności” jest bezpłatna?
Tak — pełny tekst „Wzorce głębokiej niezmienności” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu TypeScript Academy, przejdź na CoddyKit PRO. Kurs TypeScript Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Wzorce głębokiej niezmienności”?
Osiągaj głęboko readonly struktury za pomocą typów rekurencyjnych Ćwiczysz TypeScript Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć TypeScript Academy?
Nie wymagamy żadnego doświadczenia. TypeScript Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Wzorce głębokiej niezmienności”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji TypeScript Academy?
Tak. Każda lekcja TypeScript Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Właściwości readonly
- Tablice i krotki readonly
- ReadonlyArray i ReadonlyMap
- Wzorce głębokiej niezmienności