0Pricing
TypeScript Academy · Lekcja

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:

  • readonly dział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

  1. Właściwości readonly
  2. Tablice i krotki readonly
  3. ReadonlyArray i ReadonlyMap
  4. Wzorce głębokiej niezmienności
← Powrót do TypeScript Academy