Шаблоны глубокой неизменяемости
Создавайте глубоко неизменяемые структуры с помощью рекурсивных типов
«Шаблоны глубокой неизменяемости» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Свойства readonly
- Массивы и кортежи readonly
- ReadonlyArray и ReadonlyMap
- Шаблоны глубокой неизменяемости