В JavaScript оператор Spread (...) появился для удобства копирования и расширения объектов, с массивами его используют давно, а начиная с ES2018 — и с объектами. В TypeScript Spread стал не только синтаксическим улучшением, а инструментом для аккуратной типизации при манипуляциях с объектами.
Исторически в JavaScript клонирование или расширение объекта осуществляли через Object.assign, но такой подход легко приводил к потере типовой безопасности и к опасным пересечениям ключей, если структура объекта сложная.
Проблема заключается в том, что при использовании Spread для объединения/расширения объектов TypeScript выводит новый тип на основе входных структур, обрабатывая возможные конфликты ключей («последний побеждает»), но это не всегда то, что разработчик имел в виду. Особое внимание стоит уделить пересечениям с optional-полями, readonly и наличию приватных свойств в классах.
Решение: использовать Spread, позволяя компилятору TypeScript выводить результат автоматически. Для сложных кейсов важно явным образом ограничивать тип входных структур и внимательно следить за изменениями структуры типов.
Пример кода:
interface User { name: string; age: number; } interface Extra { isAdmin?: boolean; readonly city: string; } const base: User = { name: 'Ivan', age: 28 }; const extended: User & Extra = { ...base, city: 'Moscow', isAdmin: true };
Ключевые особенности:
Можно ли с помощью Spread-наследования получить "deep copy" объекта в TypeScript?
Ответ: Нет. Spread делает только поверхностное копирование (shallow copy), вложенные объекты остаются по ссылке.
const original = { user: { name: 'Anna' } }; const cloned = { ...original }; cloned.user.name = 'Maria'; // original.user.name тоже изменится
Тип будет сужаться или расширяться при Spread объектов с пересекающимися ключами, и как это влияет на аннотацию переменной?
Ответ: При Spread тип переменной расширяется, а в случае пересечения ключей побеждают свойства справа; если есть явная аннотация типа, возможно появление ошибки, если типы несовместимы.
const a = { id: 4, value: "abc" }; const b = { value: 123 }; const c: { id: number; value: number } = { ...a, ...b }; // ok
Может ли Spread применяться к классам и что произойдет с приватными свойствами?
Ответ: Spread применим только к публичным свойствам класса. Приватные (private/#) и защищённые protected поля не попадут в результирующий объект.
class Person { private id = 77; name = "Bob"; } const p = new Person(); const spreaded = { ...p }; // spreaded: { name: string }
Разработчик делает Spread значений объекта settings для расширения пользовательских опций:
const common = { theme: 'light', notifications: true }; const user = { notifications: false, signature: 'Sasha' }; const merged = { ...common, ...user };
Ожидает, что merged будет согласован по типу, но случайно допускает ошибку с типом поля signature, забыв о том, что Spread просто копирует ключи, а не валидирует их значения.
Плюсы:
Минусы:
Для объединения конфигураций используется Spread только после валидации и с аннотацией возвращаемого типа:
interface Settings { theme: "light" | "dark"; notifications: boolean; signature?: string; } function getSettings(common: Settings, specific: Partial<Settings>): Settings { return { ...common, ...specific }; }
Плюсы:
Минусы: