Destructuring și spread
Lectură estimată: 16 minute
Estimare pentru text, la un ritm de 180 de cuvinte pe minut. Timpul pentru analizarea codului, exerciții și proiecte se adaugă separat și depinde de ritmul tău.
Cum extragi și modifici date fără efecte neașteptate
Într-o aplicație, datele rareori sunt doar numere izolate. Primești un profil cu adresă și contacte, un produs cu variante sau o comandă cu o listă de poziții. Trebuie să citești câteva câmpuri, să le trimiți unei funcții și uneori să construiești o versiune modificată. Destructuring, rest și spread te ajută să exprimi aceste operații. Pentru a le folosi corect trebuie însă să înțelegi ce se copiază și ce rămâne comun.
Vom porni de la extragerea unui singur câmp și vom ajunge la actualizarea unui contact dintr-un profil. Scenariul este un formular cu „Salvează” și „Anulează”. Acest detaliu contează: dacă ai modificat deja datele originale în timpul editării, butonul de anulare nu mai are o versiune inițială din care să refacă formularul. Vom lega fiecare alegere de această problemă concretă.
Poți rula exemplele în consola browserului sau într-un fișier .mjs executat cu Node.js. Fragmentele sunt independente dacă nu se precizează altfel. La început, urmărește valorile cu console.log. După ce înțelegi rezultatul, adaugă verificări pentru valorile vechi și noi. Nu verifica doar ce s-a schimbat; verifică și ce trebuia să rămână neschimbat.
Extragerea proprietăților dintr-un obiect
Să presupunem că ai un utilizator cu proprietățile nume, oras și rol. Poți citi fiecare proprietate prin acces direct, dar când folosești mai multe câmpuri, repetarea numelui obiectului îngreunează lectura. Destructuring declară variabile locale pornind de la proprietăți. Numele din modelul de extragere corespund cheilor obiectului; ordinea lor nu trebuie să fie aceeași cu ordinea în care ai scris proprietățile.
const utilizator = { nume: 'Ana', oras: 'Cluj', rol: 'editor' };
const { rol, nume } = utilizator;
console.log(nume, rol); // Ana editorAcest cod nu șterge proprietățile din utilizator și nu mută datele dintr-un loc în altul. El creează legături locale cu valorile citite. Pentru un șir sau un număr, ai valoarea respectivă. Pentru un obiect imbricat, valoarea este o referință la acel obiect. De aceea, extragerea adresei nu înseamnă clonarea ei. Vom reveni la această diferență când discutăm actualizarea datelor.
Redenumirea locală și valorile implicite
Uneori numele din răspunsul API nu este cel pe care vrei să îl folosești în funcție. Poți redenumi local fără să modifici proprietatea originală. În modelul { nume: numeClient }, partea din stânga identifică proprietatea, iar partea din dreapta este variabila nouă. Această ordine este ușor de confundat cu sintaxa unui obiect obișnuit, de aceea citește expresia ca „extrage nume în numeClient”.
const client = { nume: 'Atelier Nord', reducere: 0 };
const { nume: numeClient, reducere = 10, oras = 'necunoscut' } = client;
console.log(numeClient); // Atelier Nord
console.log(reducere); // 0
console.log(oras); // necunoscutValoarea implicită din destructuring se folosește când valoarea extrasă este undefined. Zero rămâne zero. Null rămâne null. Aceasta este o diferență esențială față de o regulă care ar trata ambele valori null și undefined ca absență. Dacă serviciul trimite null pentru „nu știm încă”, decide separat ce face aplicația. Nu considera că egalul din model reprezintă o validare completă.
Poți testa diferența cu trei clienți: unul fără reducere, unul cu reducere undefined și unul cu reducere null. Primele două extrageri folosesc valoarea implicită; ultima păstrează null. Dacă în continuare înmulțești un preț cu această valoare fără validare, poți obține un rezultat fără sens pentru regula comercială. Extragerea confortabilă nu elimină obligația de a verifica tipul și intervalul datelor.
Extragerea din array-uri urmărește poziția
La un array, modelul de destructuring folosește pozițiile. Primul nume primește primul element, al doilea nume primește al doilea element. Această convenție este potrivită pentru o pereche cunoscută, cum ar fi coordonatele unui punct. Este mai puțin potrivită pentru date cu multe câmpuri, unde un obiect cu nume explicite poate reduce confuziile.
const coordonate = [12, 8];
const [x, y] = coordonate;
const [primul, , alTreilea] = ['A', 'B', 'C'];
console.log(x, y); // 12 8
console.log(primul, alTreilea); // A CVirgula fără nume sare peste o poziție. Dacă poziția cerută nu există, obții undefined, cu excepția unei valori implicite specificate în model. Nu presupune că extragerea garantează lungimea corectă a listei. Pentru date externe, verifică mai întâi forma de care funcția are nevoie. Un model foarte lung, cu multe poziții sărite, este de obicei un indiciu că reprezentarea datelor ar putea fi mai clară.
Aceleași trei puncte, două roluri diferite
Rest adună ceea ce rămâne într-o variabilă. Spread desfășoară elemente sau proprietăți într-o construcție nouă. Simbolul este același, dar rolul este determinat de locul în care apare. Într-un model de extragere, ...restul colectează. Într-un array nou, ...lista introduce elementele listei. Citește sensul întregii expresii, nu doar simbolul.
const [principal, ...secundare] = ['A', 'B', 'C'];
const toate = [principal, ...secundare, 'D'];
const setari = { tema: 'dark', limba: 'ro', debug: false };
const { debug, ...pentruInterfata } = setari;
console.log(secundare); // ['B', 'C']
console.log(toate); // ['A', 'B', 'C', 'D']
console.log(pentruInterfata); // { tema: 'dark', limba: 'ro' }Ultima extragere este utilă pentru a construi un obiect fără un anumit câmp, dar nu reprezintă o politică generală de protecție a datelor. Dacă trebuie să trimiți doar trei câmpuri permise către un serviciu, selectează-le explicit. Eliminarea unui câmp cunoscut nu protejează automat împotriva altor câmpuri sensibile adăugate ulterior. Contractul unui obiect destinat exteriorului merită să fie precis.
De ce copia superficială produce surprize
Să luăm un profil cu un obiect adresa. Expresia { ...profil } construiește un obiect exterior nou și copiază proprietățile proprii enumerabile. Pentru proprietatea adresa, copiază valoarea referinței. Prin urmare, originalul și copia ajung la același obiect adresă. Cuvântul „copie” descrie aici primul nivel, nu întreaga structură.
const original = { nume: 'Ana', adresa: { oras: 'Brașov' } };
const copie = { ...original };
copie.adresa.oras = 'Sibiu';
console.log(original.adresa.oras); // Sibiu
console.log(copie === original); // false
console.log(copie.adresa === original.adresa); // trueCele două comparații arată exact ce s-a întâmplat. Obiectele exterioare sunt diferite, adresa este comună. Nu există o contradicție: fiecare comparație privește un nivel diferit. Când investighezi o modificare neașteptată, desenează pe hârtie numele și obiectele spre care duc. Această reprezentare este adesea mai utilă decât repetarea apelurilor de copiere fără să identifici nivelul problemei.
Pentru schimbarea orașului, construiește atât un profil nou, cât și o adresă nouă. Păstrează celelalte proprietăți ale profilului și celelalte proprietăți ale adresei. Dacă există și un obiect coordonate care nu se modifică, acesta poate rămâne comun, cu condiția ca programul să respecte regula de a nu-l modifica ulterior pe ascuns.
const baza = { nume: 'Ana', adresa: { oras: 'Brașov', strada: 'Lalelelor' } };
const revizuit = {
...baza,
adresa: { ...baza.adresa, oras: 'Sibiu' }
};
console.log(baza.adresa.oras); // Brașov
console.log(revizuit.adresa.strada); // LalelelorOrdinea proprietăților exprimă prioritatea
Într-un obiect construit cu spread, o proprietate scrisă ulterior poate suprascrie valoarea introdusă anterior. Dacă vrei ca opțiunile utilizatorului să aibă prioritate, le adaugi după opțiunile implicite. Dacă apoi scrii o limită obligatorie, aceasta va avea prioritate față de ambele. Ordinea este o regulă a programului și trebuie să poată fi explicată.
Nu uita că un câmp prezent cu undefined poate suprascrie o valoare bună. De asemenea, două proprietăți numite aspect, care conțin fiecare obiecte, nu sunt combinate recursiv de spread. Obiectul aspect de la final înlocuiește valoarea anterioară. Pentru a combina nivelul interior, trebuie să construiești explicit și acel nivel. Unirea automată a oricăror structuri poate avea reguli complicate pentru liste, valori null și câmpuri șterse; nu inventa acele reguli din câteva puncte de sintaxă.
Parametri obiect: claritate la apel, verificări în funcție
O funcție cu mai multe opțiuni poate primi un obiect, apoi poate extrage doar ce folosește. Apelul configureaza({ limita: 20 }) este mai clar decât o listă de valori poziționale în care trebuie să memorezi ce înseamnă fiecare. Valoarea implicită pentru întregul parametru, scrisă = {}, permite apelul fără argument. Ea nu face ca apelul cu null să fie valid.
Într-o funcție publică, validează mai întâi intrarea dacă poate fi arbitrară. Destructuring foarte adânc în antetul funcției poate produce o eroare înainte să poți explica ce câmp lipsește. Pentru date incerte, extrage în pași: verifică profilul, apoi adresa, apoi orașul. Câteva rânduri în plus pot transforma o eroare tehnică vagă într-un diagnostic pe care îl înțelegi imediat.
Copierea profundă nu este o soluție universală
Poți întâlni recomandarea de a serializa și apoi de a parsa obiectul prin JSON. Această tehnică nu păstrează arbitrar toate tipurile și relațiile din memorie. Un obiect poate conține date calendaristice, valori undefined, funcții sau referințe circulare, iar rezultatul nu este garantat echivalent. Nu alege o tehnică de clonare înainte să cunoști tipurile datelor și motivul pentru care vrei independență.
Pentru starea mică din acest curs, actualizarea explicită a nivelurilor schimbate face intenția vizibilă. Dacă folosești o facilitate precum structuredClone pentru date compatibile, verifică mai întâi ce tipuri acceptă și dacă ai nevoie într-adevăr de o clonă completă. Copierea întregii structuri la fiecare apăsare de tastă poate face mai mult lucru decât este necesar și poate ascunde o organizare neclară a stării.
Rezolvarea unei probleme înainte de implementare
Ai un profil cu trei contacte și vrei să schimbi adresa de email a contactului cu id 2. Întrebările utile sunt: ce păstrez, ce schimb și ce se întâmplă dacă acel id nu există? Răspunsul pentru laborator este: păstrez profilul inițial, creez o listă nouă, creez un obiect nou doar pentru contactul ales și raportez id-ul absent ca eroare. Aceasta este deja o schiță a algoritmului, înainte să scrii map și spread.
Verificarea trebuie să acopere valoarea nouă, valoarea veche și referințele. Dacă noua adresă este corectă, dar adresa inițială s-a modificat, funcția nu respectă cerința de anulare. Dacă toate contactele dispar în afară de cel editat, ai folosit greșit o filtrare în loc de transformare. Dacă funcția acceptă un id absent fără semnal, poți raporta o salvare care nu a schimbat nimic.
Autoevaluare înainte de laborator
Explică diferența dintre extragerea unei adrese și copierea ei. Arată de ce o valoare implicită la destructuring nu înlocuiește null. Construiește o configurare în care ordinea a două spread-uri schimbă rezultatul și spune care ordine respectă cerința. În final, desenează cele trei niveluri ale profilului cu contacte și marchează obiectele care trebuie create din nou. Laboratorul următor pune aceste explicații într-o funcție completă, cu verificări pentru fiecare nivel.
Studiu de caz: setările unui catalog se pierd la combinare
Configurarea implicită a catalogului conține tema light și un obiect paginare cu limita 20 și pagina 1. Preferințele utilizatorului conțin numai paginare cu limita 50. Unirea superficială a celor două obiecte înlocuiește întregul obiect paginare cu cel din preferințe. Limita devine 50, dar pagina dispare. Programul poate ajunge să ceară o pagină undefined, deși ai crezut că ai schimbat doar limita.
Înainte să scrii corectura, stabilește regula: preferințele pot suprascrie limita, iar pagina implicită trebuie păstrată dacă nu este furnizată. Apoi construiește obiectul exterior și obiectul paginare separat. La nivelul paginare, pornești de la valorile implicite și aplici preferințele după ele. Acesta este un exemplu de combinare la două niveluri, nu o regulă universală pentru orice structură imbricată.
Testează și o preferință care conține pagina 3: rezultatul trebuie să fie limita 50 și pagina 3. Testează preferințe fără paginare: trebuie să rămână limita 20 și pagina 1. În final, testează paginare null. Nu răspunde automat printr-un truc de sintaxă. Decide mai întâi dacă null înseamnă „dezactivează paginarea”, „nu există preferință” sau „date invalide”. Aceste interpretări produc comportamente diferite și niciuna nu poate fi dedusă doar din operatorul spread.
Cum eviți suprascrierea accidentală cu undefined
Un formular poate trimite un obiect în care toate câmpurile există, dar unele au valoarea undefined. Dacă îl combini direct cu valorile implicite, aceste câmpuri pot elimina setări utile. O soluție este să construiești explicit obiectul de modificări numai din câmpurile acceptate și prezente conform contractului. Nu transforma această filtrare într-o regulă generică care elimină toate valorile falsy: ai pierde zero, false și șirul gol chiar când sunt intenționate.
Pentru fiecare câmp, scrie ce înseamnă absența și ce înseamnă o valoare explicită. De exemplu, un checkbox false poate însemna dezactivarea unei opțiuni, iar lipsa câmpului poate însemna păstrarea setării anterioare. Dacă tratezi ambele situații identic, utilizatorul nu mai poate dezactiva opțiunea. Problema este una de modelare a modificării, nu de copiere a obiectelor.
Exercițiu rezolvat: ce referințe se schimbă într-un coș
Ai un coș cu două poziții. Fiecare poziție are id, cantitate și un obiect produs cu nume și preț. Vrei să schimbi doar cantitatea primei poziții și să păstrezi coșul inițial pentru anulare. Gândește-te ce obiecte noi sunt necesare înainte să citești rezolvarea. Nu porni de la ideea că totul trebuie clonat profund.
Ai nevoie de un array nou pentru poziții și de un obiect nou pentru prima poziție. Obiectul produs al primei poziții poate rămâne comun deoarece nu îi modifici proprietățile. A doua poziție poate rămâne aceeași referință, cu aceeași condiție de a nu fi modificată ulterior. Verifici cantitatea veche, cantitatea nouă, diferența dintre array-uri și diferența dintre obiectele primei poziții. Verifici și că obiectul produs este încă același, pentru a înțelege exact ce ai copiat.
Dacă cerința se schimbă și vrei să modifici prețul produsului în varianta nouă, trebuie să construiești și un obiect produs nou pe acel traseu. Această schimbare explică de ce o soluție corectă pentru cantitate nu este automat corectă pentru orice câmp. Nivelurile copiate sunt determinate de proprietatea modificată și de garanțiile pe care vrei să le păstrezi.
Ce înseamnă o rezolvare completă
O rezolvare completă nu se termină la expresia cu spread. Ea precizează ce se întâmplă pentru un id absent, pentru o cantitate zero și pentru două poziții cu același id. Poți alege să respingi o cantitate zero sau să o interpretezi ca eliminare, dar trebuie să fie o regulă explicită. În laborator folosim identificatori stabili și semnalăm ținta absentă. Astfel poți distinge o modificare validă de un apel care nu a găsit nimic de modificat.
Păstrează aceste întrebări și când folosești ulterior o bibliotecă de gestionare a stării. Instrumentul poate simplifica sintaxa actualizării, dar nu decide ce înseamnă o modificare validă în aplicația ta. Înțelegerea referințelor și a contractelor rămâne utilă chiar dacă nu mai scrii manual fiecare nivel de spread.
Laborator explicat: actualizarea unui contact fără modificarea sursei
Imaginează-ți un formular de profil în care utilizatorul poate modifica un contact și poate apoi apăsa „Anulează”. Dacă formularul schimbă direct obiectul folosit de restul paginii, anularea nu mai poate recupera valorile inițiale. Acesta este un motiv practic pentru a înțelege copierea obiectelor. Vom păstra versiunea inițială și vom construi una nouă, schimbând numai contactul ales. Exemplul poate fi rulat într-un fișier JavaScript sau în consola browserului.
Identifică fiecare nivel care se modifică
Datele au trei niveluri relevante: profilul, lista de contacte și obiectul contactului. O copie a profilului nu copiază automat lista, iar o copie a listei nu copiază automat obiectele din ea. În schimb, nu trebuie să clonezi fiecare obiect din aplicație. Construiește obiecte noi doar pe traseul proprietății schimbate. Contactele neatinse pot fi păstrate prin referință, dacă programul respectă regula de a nu le modifica direct.
const profil = {
nume: 'Ana',
contacte: [
{ id: 1, tip: 'email', valoare: 'ana@example.test' },
{ id: 2, tip: 'telefon', valoare: '0700000000' }
]
};
function schimbaContact(profil, id, valoare) {
if (!profil.contacte.some(contact => contact.id === id)) {
throw new Error('Contact inexistent');
}
return {
...profil,
contacte: profil.contacte.map(contact =>
contact.id === id ? { ...contact, valoare } : contact
)
};
}
const nou = schimbaContact(profil, 1, 'nou@example.test');
console.assert(nou !== profil);
console.assert(nou.contacte !== profil.contacte);
console.assert(nou.contacte[0] !== profil.contacte[0]);
console.assert(nou.contacte[1] === profil.contacte[1]);
console.assert(profil.contacte[0].valoare === 'ana@example.test');Prima verificare confirmă un profil nou. A doua confirmă o listă nouă. A treia arată că obiectul modificat este separat, iar a patra demonstrează reutilizarea contactului neschimbat. Ultima verificare este cea care protejează comportamentul de anulare. Dacă ai testa numai valoarea din nou, ai putea rata faptul că și profilul inițial a fost alterat. Într-un test automat, folosește aserțiuni care opresc execuția la eșec; console.assert este util pentru explorare, dar nu reprezintă singur o suită de teste.
Destructuring la granița unei funcții
Un parametru obiect face apelul mai ușor de citit când există mai multe opțiuni. Totuși, un contract clar trebuie să spună ce valori sunt acceptate. Compară funcția de mai jos cu o versiune care ar primi trei argumente poziționale: la apel vezi direct ce opțiune modifici. Valoarea implicită a întregului parametru permite apelul fără argument. Ea nu validează orice intrare și nu înlocuiește null cu un obiect.
function configureaza({ tema = 'light', limita = 10 } = {}) {
return { tema, limita };
}
console.assert(configureaza().limita === 10);
console.assert(configureaza({ limita: 0 }).limita === 0);
console.assert(configureaza({ limita: null }).limita === null);În proiectul laboratorului, limita zero ar putea însemna că lista este ascunsă, iar null ar putea fi o intrare invalidă. Adaugă validarea în funcție de această decizie. Numele unei proprietăți poate fi redenumit în variabila locală, fără să fie modificat obiectul sursă. De exemplu, extragerea câmpului valoare ca valoareInitiala ajută când compari înainte și după. Folosește redenumirea pentru claritate, nu pentru a ascunde denumiri incoerente în date.
Combinarea opțiunilor și ordinea suprascrierii
Într-o configurare, scrie mai întâi opțiunile de bază și apoi opțiunile specifice dacă acestea trebuie să aibă prioritate. Dar definește separat ce înseamnă o proprietate prezentă cu undefined: poate suprascrie o valoare bună. De asemenea, combinarea la primul nivel a două obiecte care conțin un obiect aspect poate înlocui acel obiect întreg. Pentru a păstra culoarea când se schimbă doar dimensiunea, combină explicit și proprietățile din aspect.
Exercițiu și rezolvare de control
Adaugă un al treilea contact folosind o listă nouă, apoi modifică numai valoarea lui. Lista originală trebuie să rămână cu două contacte. Apelează funcția cu id 99 și verifică eroarea „Contact inexistent”. Pentru un rezultat complet, arată lungimile ambelor liste, valoarea veche, valoarea nouă și comparațiile de referințe. Dacă vrei ca un id absent să nu fie eroare, documentează alt contract; nu lăsa comportamentul să apară accidental din map.
Verifică salvarea și anularea ca două acțiuni diferite
Păstrează profil ca valoare confirmată și nou ca propunere de modificare. Acțiunea de anulare poate elimina propunerea și poate redesena formularul din profil. Acțiunea de salvare trebuie să trimită propunerea către serviciu și să confirme schimbarea numai după un răspuns reușit. Copierea obiectelor rezolvă independența datelor în memorie; nu garantează că serverul a salvat corect sau că alt utilizator nu a modificat între timp același profil.
Simulează o salvare eșuată și verifică dacă ai păstrat ambele valori. Utilizatorul trebuie să poată reîncerca fără să reintroducă toate câmpurile. Pentru acest experiment, este suficient să păstrezi o variabilă pentru profilul confirmat, una pentru propunere și un mesaj de eroare. Nu amesteca în același obiect datele confirmate și modificările temporare fără să precizezi care versiune este afișată. Compararea celor două versiuni este și punctul de pornire pentru evidențierea câmpurilor schimbate într-un formular.
Documentație pentru aprofundare
Ți-a plăcut lecția? Continuă cursul complet
Ai citit o lecție gratuită. Celelalte lecții, aplicațiile practice și materialele se deblochează odată cu cursul.
149 lei
Plată unică, acces pe viață la toate cele 7 lecții.
Vezi ce conține cursul →