W JavaScript operator Spread (...) pojawił się dla wygody kopiowania i rozszerzania obiektów, z tablicami używa się go od dawnych czasów, a od ES2018 – także z obiektami. W TypeScript Spread stał się nie tylko poprawą składniową, ale narzędziem do starannej typizacji podczas manipulacji obiektami.
Historycznie w JavaScript klonowanie lub rozszerzanie obiektu realizowano przez Object.assign, ale takie podejście łatwo prowadziło do utraty bezpieczeństwa typów i niebezpiecznych przecięć kluczy, jeśli struktura obiektu była skomplikowana.
Problem polega na tym, że przy użyciu Spread do łączenia/rozszerzania obiektów TypeScript wyprowadza nowy typ na podstawie wejściowych struktur, przetwarzając możliwe konflikty kluczy („ostatni wygrywa”), ale nie zawsze jest to to, co miał na myśli programista. Szczególną uwagę należy zwrócić na przecięcia z polami opcjonalnymi, readonly oraz obecność prywatnych właściwości w klasach.
Rozwiązanie: używaj Spread, pozwalając kompilatorowi TypeScript automatycznie wyprowadzać wynik. W przypadku skomplikowanych przypadków ważne jest wyraźne ograniczenie typu wejściowych struktur oraz uważne śledzenie zmian struktury typów.
Przykład kodu:
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 };
Kluczowe cechy:
Czy można za pomocą dziedziczenia Spread uzyskać „deep copy” obiektu w TypeScript?
Odpowiedź: Nie. Spread wykonuje tylko płytkie kopiowanie (shallow copy), zagnieżdżone obiekty pozostają po odniesieniu.
const original = { user: { name: 'Anna' } }; const cloned = { ...original }; cloned.user.name = 'Maria'; // original.user.name również się zmieni
Czy typ będzie zawężany czy rozszerzany przy Spread obiektów z nakładającymi się kluczami i jak to wpływa na adnotację zmiennej?
Odpowiedź: Przy Spread typ zmiennej się rozszerza, a w przypadku nakładania kluczy wygrywają właściwości z prawej; jeśli istnieje wyraźna adnotacja typu, możliwe pojawienie się błędu, jeśli typy są niespójne.
const a = { id: 4, value: "abc" }; const b = { value: 123 }; const c: { id: number; value: number } = { ...a, ...b }; // ok
Czy Spread może być stosowany do klas i co się stanie z prywatnymi właściwościami?
Odpowiedź: Spread można stosować tylko do publicznych właściwości klasy. Prywatne (private/#) i chronione protected pola nie trafią do wynikowego obiektu.
class Person { private id = 77; name = "Bob"; } const p = new Person(); const spreaded = { ...p }; // spreaded: { name: string }
Programista wykonuje Spread wartości obiektu settings w celu rozszerzenia opcji użytkownika:
const common = { theme: 'light', notifications: true }; const user = { notifications: false, signature: 'Sasha' }; const merged = { ...common, ...user };
Oczekuje, że merged będzie zgodny z typem, ale przypadkowo dopuszcza błąd z typem pola signature, zapominając, że Spread po prostu kopiuje klucze, a nie weryfikuje ich wartości.
Plusy:
Minusy:
Do łączenia konfiguracji używa się Spread tylko po walidacji i z adnotacją zwracanego typu:
interface Settings { theme: "light" | "dark"; notifications: boolean; signature?: string; } function getSettings(common: Settings, specific: Partial<Settings>): Settings { return { ...common, ...specific }; }
Plusy:
Minusy: