En JavaScript, el operador Spread (...) se introdujo para facilitar la copia y extensión de objetos; con los arreglos se ha utilizado durante mucho tiempo, y a partir de ES2018 también con objetos. En TypeScript, Spread se ha convertido no solo en una mejora sintáctica, sino en una herramienta para una tipificación cuidadosa al manipular objetos.
Históricamente, la clonación o extensión de un objeto se realizaba mediante Object.assign, pero este enfoque podía fácilmente llevar a la pérdida de la seguridad de tipos y a peligrosas intersecciones de claves, especialmente si la estructura del objeto es compleja.
El problema es que al usar Spread para combinar/extender objetos, TypeScript infiere un nuevo tipo basado en las estructuras de entrada, manejando posibles conflictos de claves ("el último gana"), pero esto no siempre es lo que el desarrollador tenía en mente. Se debe prestar especial atención a las intersecciones con campos opcionales, readonly y a la existencia de propiedades privadas en las clases.
Solución: utilizar Spread, permitiendo que el compilador TypeScript infiera el resultado automáticamente. Para casos complejos, es importante limitar explícitamente el tipo de las estructuras de entrada y estar atento a los cambios en la estructura de tipos.
Ejemplo de código:
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 };
Características clave:
¿Se puede obtener una "deep copy" de un objeto en TypeScript mediante herencia Spread?
Respuesta: No. Spread solo hace copias superficiales (shallow copy), los objetos anidados permanecen por referencia.
const original = { user: { name: 'Anna' } }; const cloned = { ...original }; cloned.user.name = 'Maria'; // original.user.name también cambiará
¿El tipo se reducirá o se extenderá al usar Spread en objetos con claves en conflicto, y cómo afecta eso a la anotación de la variable?
Respuesta: Al usar Spread, el tipo de la variable se extiende, y en el caso de intersecciones de claves, prevalecen las propiedades de la derecha; si hay una anotación de tipo explícita, puede aparecer un error si los tipos no son compatibles.
const a = { id: 4, value: "abc" }; const b = { value: 123 }; const c: { id: number; value: number } = { ...a, ...b }; // ok
¿Se puede aplicar Spread a clases y qué ocurrirá con las propiedades privadas?
Respuesta: Spread solo se aplica a las propiedades públicas de la clase. Las propiedades privadas (private/#) y protegidas protected no estarán en el objeto resultante.
class Person { private id = 77; name = "Bob"; } const p = new Person(); const spreaded = { ...p }; // spreaded: { name: string }
El desarrollador utiliza Spread para extender las opciones de usuario del objeto settings:
const common = { theme: 'light', notifications: true }; const user = { notifications: false, signature: 'Sasha' }; const merged = { ...common, ...user };
Él espera que merged sea coherente en tipo, pero accidentalmente comete un error con el tipo del campo signature, olvidando que Spread simplemente copia las claves y no valida sus valores.
Ventajas:
Desventajas:
Para combinar configuraciones, se utiliza Spread solo después de la validación y con anotación de tipo de retorno:
interface Settings { theme: "light" | "dark"; notifications: boolean; signature?: string; } function getSettings(common: Settings, specific: Partial<Settings>): Settings { return { ...common, ...specific }; }
Ventajas:
Desventajas: