0Pricing
TypeScript Academy · Урок

Шаблоны глубокой неизменяемости

Создавайте глубоко неизменяемые структуры с помощью рекурсивных типов

«Шаблоны глубокой неизменяемости» — бесплатный урок TypeScript Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения TypeScript Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс TypeScript Academy содержит 4 уроков всего.

Поверхностная и глубокая неизменяемость

Модификатор readonly является поверхностным: он защищает верхний уровень, но не вложенные объекты. Глубокая неизменяемость означает, что доступными только для чтения являются все уровни, вплоть до самого глубокого. Для этого требуется дополнительная работа.

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);

Почему поверхностной защиты недостаточно

При управлении состоянием и общими данными поверхностная защита только для чтения оставляет вложенные поля открытыми для изменений. Одно незамеченное изменение глубоко внутри структуры может привести к трудно отслеживаемым ошибкам.

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);

Рекурсивный тип DeepReadonly

Мы можем создать отображаемый тип, который рекурсивно применяет readonly. DeepReadonly<T> проходит по каждому свойству и, если оно является объектом, рекурсивно обрабатывает его содержимое.

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};
console.log('DeepReadonly defined');

Применение DeepReadonly

Обёртывание типа в DeepReadonly делает неизменяемыми и вложенные свойства. Теперь даже s.user.name нельзя переназначить — защита распространяется на все уровни.

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);

Как работает отображаемый тип

Конструкция [K in keyof T] перебирает каждый ключ. Префикс readonly блокирует каждый из них. Условие T[K] extends object ? ... : T[K] определяет, нужно ли продолжить рекурсию или остановиться на примитивном типе.

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);

Защита действует только во время компиляции

DeepReadonly обеспечивает неизменяемость в системе типов, но не во время выполнения. Объект JavaScript технически всё ещё можно изменить через пути без типизации, однако Ваш типизированный код полностью защищён.

// 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');

Заморозка вложенных объектов во время выполнения

Для настоящей неизменяемости во время выполнения используйте Object.freeze. Он предотвращает добавление, удаление и изменение свойств, но, как и readonly, по умолчанию действует поверхностно.

const config = Object.freeze({ host: 'localhost', port: 8080 });
console.log(config.port);
// In strict mode, config.port = 1 throws; otherwise silently ignored.

Вспомогательная функция глубокой заморозки

Чтобы выполнить глубокую заморозку во время выполнения, рекурсивно пройдите по вложенным объектам и заморозьте каждый из них. Такой подход естественно сочетается с типом DeepReadonly для обеспечения неизменяемости на всех уровнях.

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);

Неизменяемость для более безопасного состояния

Глубокая неизменяемость — основа предсказуемого состояния. Когда данные нельзя изменить на месте, обновления должны создавать новые объекты, благодаря чему изменения становятся явными и их легко отслеживать.

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);

Шаблон неизменяемого обновления

Работая с данными только для чтения, Вы создаёте новые версии с помощью разворачивания данных. Каждое обновление создаёт новый объект, поэтому предыдущие состояния сохраняются — это особенно полезно для отмены действий, отладки с перемещением во времени и обнаружения изменений.

type State = { readonly items: readonly string[] };
const s: State = { items: ['a'] };
const updated: State = { items: [...s.items, 'b'] };
console.log(s.items, updated.items);

Сочетание безопасности типов и выполнения

Самый надёжный подход использует оба механизма: DeepReadonly обеспечивает гарантии во время компиляции, а deepFreeze — контроль во время выполнения. Вместе они делают случайное изменение данных почти невозможным.

function freeze<T>(obj: T): T {
  return Object.freeze(obj);
}
const settings = freeze({ theme: 'dark', version: 2 });
console.log(settings.theme, settings.version);

Быстрая проверка

Проверьте, насколько хорошо Вы поняли глубокую неизменяемость.

Итоги: глубокая неизменяемость

Вы узнали, что:

  • readonly является поверхностным: вложенные поля остаются изменяемыми.
  • Рекурсивный отображаемый тип DeepReadonly<T> применяет readonly на каждом уровне.
  • Он действует только во время компиляции; для контроля во время выполнения используйте глубокий Object.freeze.
  • Неизменяемость обеспечивает безопасное и предсказуемое состояние благодаря шаблонам копирования при обновлении.

На этом завершается направление TypeScript Academy по расширенным типам и неизменяемости.

type State = { readonly value: number };
const s: State = { value: 1 };
const next: State = { ...s, value: s.value + 1 };
console.log(s.value, next.value);

Часто задаваемые вопросы

Урок «Шаблоны глубокой неизменяемости» бесплатный?

Да — полный текст урока «Шаблоны глубокой неизменяемости» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс TypeScript Academy, подпишись на CoddyKit PRO. Курс TypeScript Academy содержит 4 уроков всего.

Чему я научусь в уроке «Шаблоны глубокой неизменяемости»?

Создавайте глубоко неизменяемые структуры с помощью рекурсивных типов Ты практикуешь TypeScript Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать TypeScript Academy?

Предыдущий опыт не требуется. TypeScript Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Шаблоны глубокой неизменяемости»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке TypeScript Academy?

Да. Каждый урок TypeScript Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Свойства readonly
  2. Массивы и кортежи readonly
  3. ReadonlyArray и ReadonlyMap
  4. Шаблоны глубокой неизменяемости
← Назад к TypeScript Academy