10 funcții Lodash importante pentru dezvoltatorii JavaScript

Pentru programatorii JavaScript, Lodash nu mai are nevoie de o prezentare amplă. Totuși, această bibliotecă este extrem de bogată și uneori pare copleșitoare. Dar nu mai este cazul acum!

Lodash, Lodash, Lodash… de unde să încep? 🤔

A fost o perioadă în care ecosistemul JavaScript era încă în faza de dezvoltare; ar putea fi comparat cu Vestul Sălbatic sau cu o junglă, unde se întâmplau multe, dar existau puține soluții pentru problemele zilnice ale programatorilor și pentru sporirea productivității.

Apoi, Lodash a apărut și a fost ca o mare de apă care a inundat totul. De la nevoi simple, cum ar fi sortarea, până la transformări complexe ale structurilor de date, Lodash a venit cu o multitudine de funcționalități care au transformat viața programatorilor JS într-o adevărată bucurie.

Salut, Lodash!

Unde se află Lodash astăzi? Ei bine, încă mai are toate funcționalitățile pe care le-a oferit la început, dar se pare că și-a pierdut din popularitate în comunitatea JavaScript. De ce? Mă gândesc la câteva motive:

  • Unele funcții din Lodash au fost (și încă sunt) lente atunci când sunt aplicate listelor mari. Chiar dacă acest lucru nu a afectat 95% din proiecte, dezvoltatorii influenți din restul de 5% au creat o imagine proastă pentru Lodash, iar acest efect s-a extins.
  • Există o tendință în ecosistemul JS (și s-ar putea spune același lucru și despre programatorii Golang), unde aroganța este mai frecventă decât ar trebui. Astfel, folosirea unei biblioteci ca Lodash este considerată o greșeală și este criticată pe forumuri ca StackOverflow atunci când oamenii sugerează astfel de soluții („Ce?! Folosesc o bibliotecă întreagă pentru așa ceva? Pot combina filter() cu reduce() pentru a obține același rezultat într-o funcție simplă!”).
  • Lodash este o bibliotecă veche. Cel puțin după standardele JS. A fost lansată în 2012, deci au trecut aproape zece ani. API-ul a fost stabil și nu se pot adăuga multe funcționalități noi în fiecare an (pur și simplu pentru că nu este nevoie), ceea ce generează plictiseală pentru programatorul JS obișnuit.

Din punctul meu de vedere, a nu folosi Lodash este o pierdere semnificativă pentru codul nostru JavaScript. S-a dovedit a fi o soluție elegantă și lipsită de erori pentru problemele zilnice pe care le întâlnim la locul de muncă, iar folosirea ei va face codul nostru mai ușor de citit și de întreținut.

Acestea fiind spuse, haideți să explorăm câteva dintre funcțiile comune (sau nu!) ale Lodash și să vedem cât de incredibil de utilă și valoroasă este această bibliotecă.

Clonare… profundă!

Deoarece obiectele sunt transmise prin referință în JavaScript, dezvoltatorii întâmpină dificultăți atunci când doresc să cloneze ceva, sperând că noul set de date va fi diferit.

let people = [
  {
    name: 'Arnold',
    specialization: 'C++',
  },
  {
    name: 'Phil',
    specialization: 'Python',
  },
  {
    name: 'Percy',
    specialization: 'JS',
  },
];

// Căutăm persoanele care scriu în C++
let folksDoingCpp = people.filter((person) => person.specialization == 'C++');

// Le convertim la JS!
for (person of folksDoingCpp) {
  person.specialization = 'JS';
}

console.log(folksDoingCpp);
// [ { name: 'Arnold', specialization: 'JS' } ]

console.log(people);
/*
[
  { name: 'Arnold', specialization: 'JS' },
  { name: 'Phil', specialization: 'Python' },
  { name: 'Percy', specialization: 'JS' }
]
*/

Observați cum, din neștiință și în ciuda intențiilor bune, matricea originală de persoane a fost modificată (specializarea lui Arnold s-a schimbat de la C++ la JS) – o lovitură majoră pentru integritatea sistemului software! Avem nevoie de o modalitate de a face o copie reală (profundă) a matricei originale.

Salut, Dave! Fă cunoștință cu Dave!

Se poate argumenta că acesta este un mod „prost” de a scrie cod în JS; totuși, realitatea este puțin mai complicată. Da, avem la dispoziție operatorul de destructurare, dar oricine a încercat să destructurizeze obiecte și matrice complexe cunoaște dificultatea. Apoi, există ideea de a folosi serializarea și deserializarea (poate JSON) pentru a obține o copiere profundă, dar acest lucru va face codul mai greu de citit.

În schimb, uitați cât de elegantă și concisă este soluția atunci când folosim Lodash:

const _ = require('lodash');

let people = [
  {
    name: 'Arnold',
    specialization: 'C++',
  },
  {
    name: 'Phil',
    specialization: 'Python',
  },
  {
    name: 'Percy',
    specialization: 'JS',
  },
];

let peopleCopy = _.cloneDeep(people);

// Căutăm persoanele care scriu în C++
let folksDoingCpp = peopleCopy.filter(
  (person) => person.specialization == 'C++'
);

// Le convertim la JS!
for (person of folksDoingCpp) {
  person.specialization = 'JS';
}

console.log(folksDoingCpp);
// [ { name: 'Arnold', specialization: 'JS' } ]

console.log(people);
/*
[
  { name: 'Arnold', specialization: 'C++' },
  { name: 'Phil', specialization: 'Python' },
  { name: 'Percy', specialization: 'JS' }
]
*/

Observați cum matricea de persoane nu este modificată după clonarea profundă (Arnold este încă specializat în C++). Dar, cel mai important, codul este ușor de înțeles.

Eliminarea duplicatelor dintr-o matrice

Eliminarea duplicatelor dintr-o matrice sună ca o problemă excelentă de interviu/de tablă albă (nu uitați, dacă aveți dubii, folosiți un hash map!). Desigur, puteți scrie oricând o funcție personalizată pentru a face acest lucru, dar ce se întâmplă dacă întâlniți mai multe scenarii diferite în care trebuie să faceți matricele unice? Ați putea scrie încă câteva funcții pentru asta (și riscați să întâlniți erori subtile) sau puteți utiliza Lodash!

Primul nostru exemplu de matrice unică este destul de banal, dar demonstrează viteza și fiabilitatea pe care Lodash le oferă. Imaginați-vă că faceți asta scriind singuri toată logica personalizată!

const _ = require('lodash');

const userIds = [12, 13, 14, 12, 5, 34, 11, 12];
const uniqueUserIds = _.uniq(userIds);
console.log(uniqueUserIds);
// [ 12, 13, 14, 5, 34, 11 ]

Observați că matricea finală nu este sortată, ceea ce, desigur, nu este o problemă aici. Acum, să ne imaginăm un scenariu mai complicat: avem o serie de utilizatori pe care i-am extras de undeva, dar vrem să ne asigurăm că sunt doar utilizatori unici. Simplu cu Lodash!

const _ = require('lodash');

const users = [
  { id: 10, name: 'Phil', age: 32 },
  { id: 8, name: 'Jason', age: 44 },
  { id: 11, name: 'Rye', age: 28 },
  { id: 10, name: 'Phil', age: 32 },
];

const uniqueUsers = _.uniqBy(users, 'id');
console.log(uniqueUsers);
/*
[
  { id: 10, name: 'Phil', age: 32 },
  { id: 8, name: 'Jason', age: 44 },
  { id: 11, name: 'Rye', age: 28 }
]
*/

În acest exemplu, am folosit metoda uniqBy() pentru a-i spune lui Lodash că dorim ca obiectele să fie unice în funcție de proprietatea id. Într-o singură linie, am exprimat ceea ce ar fi putut dura 10-20 de rânduri și am introdus mai mult spațiu pentru erori!

Există multe alte opțiuni disponibile pentru a face elementele unice în Lodash și vă încurajez să consultați documentația.

Diferența a două matrice

Uniune, diferență etc. ar putea suna ca termeni potriviți pentru orele plictisitoare de liceu despre Teoria Seturilor, dar apar frecvent în practică. Este obișnuit să ai o listă și să dorești să o îmbini cu o altă listă sau să găsești elementele unice în comparație cu o altă listă. Pentru aceste scenarii, funcția de diferență este perfectă.

Salut, A! La revedere, B!

Să începem explorarea diferenței cu un scenariu simplu: ai primit o listă cu toate ID-urile de utilizator din sistem și o listă cu cele ale căror conturi sunt active. Cum găsești ID-urile inactive? Simplu, nu?

const _ = require('lodash');

const allUserIds = [1, 3, 4, 2, 10, 22, 11, 8];
const activeUserIds = [1, 4, 22, 11, 8];

const inactiveUserIds = _.difference(allUserIds, activeUserIds);
console.log(inactiveUserIds);
// [ 3, 2, 10 ]

Și ce se întâmplă dacă, așa cum se întâmplă într-un context mai realist, trebuie să lucrezi cu o serie de obiecte în loc de primitive simple? Ei bine, Lodash are o metodă excelentă, differenceBy(), pentru asta!

const allUsers = [
  { id: 1, name: 'Phil' },
  { id: 2, name: 'John' },
  { id: 3, name: 'Rogg' },
];
const activeUsers = [
  { id: 1, name: 'Phil' },
  { id: 2, name: 'John' },
];
const inactiveUsers = _.differenceBy(allUsers, activeUsers, 'id');
console.log(inactiveUsers);
// [ { id: 3, name: 'Rogg' } ]

Frumos, nu-i așa?!

Pe lângă diferență, Lodash oferă și alte metode pentru operații obișnuite cu seturi: uniune, intersecție etc.

Aplatizarea matricelor

Necesitatea de a aplatiza o matrice apare destul de des. Un caz de utilizare este următorul: ai primit un răspuns API și trebuie să aplici un combo map() și filter() pe o listă complexă de obiecte/matrice imbricate pentru a extrage, să zicem, ID-uri de utilizator și acum ai matrice de matrice. Iată un exemplu de cod care descrie această situație:

const orderData = {
  internal: [
    { userId: 1, date: '2021-09-09', amount: 230.0, type: 'prepaid' },
    { userId: 2, date: '2021-07-07', amount: 130.0, type: 'prepaid' },
  ],
  external: [
    { userId: 3, date: '2021-08-08', amount: 30.0, type: 'postpaid' },
    { userId: 4, date: '2021-06-06', amount: 330.0, type: 'postpaid' },
  ],
};

// Căutăm ID-urile utilizatorilor care au plasat comenzi postplătite (interne sau externe)
const postpaidUserIds = [];

for (const [orderType, orders] of Object.entries(orderData)) {
  postpaidUserIds.push(orders.filter((order) => order.type === 'postpaid'));
}
console.log(postpaidUserIds);

Puteți ghici cum arată acum postPaidUserIds? Un indiciu: arată oribil!

[
  [],
  [
    { userId: 3, date: '2021-08-08', amount: 30, type: 'postpaid' },
    { userId: 4, date: '2021-06-06', amount: 330, type: 'postpaid' }
  ]
]

Acum, dacă sunteți o persoană sensibilă, nu doriți să scrieți o logică personalizată pentru a extrage obiectele de comandă și a le așeza frumos într-o singură matrice. Utilizați metoda flatten() și bucurați-vă de rezultat:

const flatUserIds = _.flatten(postpaidUserIds);
console.log(flatUserIds);
/*
[
  { userId: 3, date: '2021-08-08', amount: 30, type: 'postpaid' },
  { userId: 4, date: '2021-06-06', amount: 330, type: 'postpaid' }
]
*/

Rețineți că flatten() merge doar un nivel în adâncime. Adică, dacă obiectele tale sunt imbricate la două, trei sau mai multe niveluri, flatten() te va dezamăgi. În aceste cazuri, Lodash are metoda flattenDeep(), dar fiți avertizat că aplicarea acestei metode pe structuri foarte mari poate încetini lucrurile (deoarece, în spate, există o operație recursivă).

Obiectul/matricea este gol?

Datorită modului în care valorile și tipurile „false” funcționează în JavaScript, uneori, ceva la fel de simplu ca verificarea golului generează o teamă existențială.

Cum verifici dacă o matrice este goală? Puteți verifica dacă lungimea sa este 0 sau nu. Acum, cum verifici dacă un obiect este gol? Ei bine… stai puțin! Aici apare acel sentiment neplăcut și exemplele JavaScript care conțin lucruri precum [] == false și {} == false încep să ne deranjeze. Când sunteți sub presiune să livrați o funcție, astfel de dificultăți sunt ultimul lucru de care aveți nevoie – vă vor face codul greu de înțeles și vor introduce incertitudine în setul de teste.

Lucrul cu datele lipsă

În lumea reală, datele nu ne ascultă; oricât de mult am dori, rareori sunt ordonate și corecte. Un exemplu tipic este lipsa obiectelor/matricelor nule într-o structură mare de date primită ca răspuns API.

Să presupunem că am primit următorul obiect ca răspuns API:

const apiResponse = {
  id: 33467,
  paymentRefernce: 'AEE3356T68',
  // Obiectul `order` lipsește
  processedAt: `2021-10-10 00:00:00`,
};

După cum se vede, de obicei primim un obiect de comandă în răspunsul de la API, dar nu este întotdeauna cazul. Deci, ce se întâmplă dacă avem cod care se bazează pe acest obiect? O modalitate ar fi să codificăm în mod defensiv, dar, în funcție de cât de imbricat este obiectul de comandă, vom scrie în curând cod foarte urât dacă dorim să evităm erorile la rulare:

if (
  apiResponse.order &&
  apiResponse.order.payee &&
  apiResponse.order.payee.address
) {
  console.log(
    'Comanda a fost trimisă la codul poștal: ' +
      apiResponse.order.payee.address.zipCode
  );
}

🤢🤢 Da, foarte urât de scris, foarte urât de citit, foarte urât de întreținut și așa mai departe. Din fericire, Lodash are o modalitate simplă de a gestiona astfel de situații.

const zipCode = _.get(apiResponse, 'order.payee.address.zipCode');
console.log('Comanda a fost trimisă la codul poștal: ' + zipCode);
// Comanda a fost trimisă la codul poștal: undefined

Există, de asemenea, opțiunea fantastică de a oferi o valoare implicită în loc ca rezultatul să fie nedefinit pentru elementele lipsă:

const zipCode2 = _.get(apiResponse, 'order.payee.address.zipCode', 'NA');
console.log('Comanda a fost trimisă la codul poștal: ' + zipCode2);
// Comanda a fost trimisă la codul poștal: NA

Nu știu despre voi, dar get() este unul dintre acele lucruri care îmi aduc lacrimi de fericire. Nu este nimic special. Nu există o sintaxă sau opțiuni de memorat, dar uitați-vă la suferința colectivă pe care o poate atenua! 😇

Debouncing

Dacă nu sunteți familiarizat, debouncing este un concept comun în dezvoltarea frontend. Ideea este că uneori este util să se lanseze o acțiune nu imediat, ci după o anumită perioadă de timp (de obicei câteva milisecunde). Ce înseamnă asta? Iată un exemplu.

Imaginați-vă un site de comerț electronic cu o bară de căutare (ei bine, orice site web/aplicație web în zilele noastre!). Pentru o experiență de utilizator mai bună, nu dorim ca utilizatorul să fie nevoit să apese Enter (sau, mai rău, să apese butonul „căutare”) pentru a afișa sugestii/previzualizări pe baza termenului de căutare. Dar răspunsul evident este puțin problematic: dacă adăugăm un ascultător de evenimente la onChange() pentru bara de căutare și lansăm un apel API pentru fiecare apăsare de tastă, am crea un coșmar pentru backend-ul nostru; ar fi prea multe apeluri inutile (de exemplu, dacă se caută „perie albă pentru covor”, ar fi 18 solicitări în total!) și aproape toate acestea ar fi irelevante deoarece introducerea utilizatorului nu s-a terminat.

Soluția constă în debouncing, iar ideea este următoarea: nu trimiteți un apel API imediat ce textul se schimbă. Așteptați ceva timp (să zicem, 200 de milisecunde) și dacă până în acel moment există o altă apăsare a tastei, anulați contorul de timp anterior și începeți să așteptați din nou. Ca rezultat, numai atunci când utilizatorul se oprește (fie pentru că se gândește, fie pentru că a terminat și așteaptă un răspuns) trimitem o solicitare API către backend.

Strategia generală pe care am descris-o este complicată și nu voi intra în detalii despre sincronizarea gestionării și anulării temporizatorului; totuși, procesul real de debouncing este foarte simplu dacă folosiți Lodash.

const _ = require('lodash');
const axios = require('axios');

// Acesta este un API real pentru câini, apropo!
const fetchDogBreeds = () =>
  axios
    .get('https://dog.ceo/api/breeds/list/all')
    .then((res) => console.log(res.data));

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 1000); // După o secundă
debouncedFetchDogBreeds(); // Afișează datele după un timp

Dacă vă gândiți că setTimeout() ar fi făcut același lucru, ei bine, este mai mult de atât! Debouncing-ul din Lodash vine cu multe caracteristici puternice; de exemplu, s-ar putea dori să vă asigurați că retragerea nu este nelimitată. Adică, chiar dacă există o apăsare de tastă de fiecare dată când funcția este pe cale să se declanșeze (anulând astfel întregul proces), poate doriți să vă asigurați că apelul API este efectuat oricum după, să zicem, două secunde. Pentru aceasta, Lodash debounce() are opțiunea maxWait:

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 150, { maxWait: 2000 }); // Debounce timp de 250ms, dar trimiteți cererea API oricum după 2 secunde

Consultați documentația oficială pentru mai multe detalii. Este plină de informații importante!

Eliminarea valorilor dintr-o matrice

Nu știu despre voi, dar urăsc să scriu cod pentru eliminarea elementelor dintr-o matrice. În primul rând, trebuie să obțin indexul elementului, să verific dacă indexul este valid și, dacă da, să apelez metoda splice() și așa mai departe. Nu-mi amintesc niciodată sintaxa și, prin urmare, trebuie să caut informațiile tot timpul, iar la sfârșit, am un sentiment neplăcut că am lăsat să se strecoare un bug prostesc.

const greetings = ['hello', 'hi', 'hey', 'wave', 'hi'];
_.pull(greetings, 'wave', 'hi');
console.log(greetings);
// [ 'hello', 'hey' ]

Vă rugăm să rețineți două lucruri:

  • Matricea originală a fost modificată în timpul procesului.
  • Metoda pull() elimină toate instanțele, chiar dacă există duplicate.

Există o altă metodă asociată numită pullAll() care acceptă o matrice ca al doilea parametru, facilitând eliminarea mai multor elemente simultan. Desigur, am putea folosi doar pull() cu un operator de împrăștiere, dar nu uitați că Lodash a apărut într-o perioadă în care operatorul de împrăștiere nu era nici măcar o propunere în limbaj!

const greetings2 = ['hello', 'hi', 'hey', 'wave', 'hi'];
_.pullAll(greetings2, ['wave', 'hi']);
console.log(greetings2);
// [ 'hello', 'hey' ]

Ultimul index al unui element

Metoda nativă indexOf() a lui JavaScript este excelentă, cu excepția cazului în care sunteți interesat să scanați matricea din direcția opusă! Și încă o dată, da, ați putea scrie o buclă de decrementare și să găsiți elementul, dar de ce să nu folosiți o tehnică mult mai elegantă?

Iată o soluție rapidă Lodash folosind metoda lastIndexOf():

const integers = [2, 4, 1, 6, -1, 10, 3, -1, 7];
const index = _.lastIndexOf(integers, -1);
console.log(index); // 7

Din păcate, nu există nicio variantă a acestei metode în care să putem căuta obiecte complexe sau să transmitem o funcție de căutare personalizată.

Zip. Dezarhivați!

Dacă nu ați lucrat în Python, zip/unzip este un utilitar pe care s-ar putea să nu-l observați sau să-l imaginați niciodată în întreaga carieră de programator JavaScript. Și poate dintr-un motiv întemeiat: rareori există o nevoie disperată de zip/unzip ca și pentru filter() etc. Totuși, este unul dintre cele mai bune utilitare mai puțin cunoscute și vă poate ajuta să creați cod concis în anumite situații.

Spre deosebire de cum sună, zip/unzip nu are nimic de-a face cu compresia. În schimb, este o operațiune de grupare în care matricele de aceeași lungime pot fi convertite într-o singură matrice de matrice cu elemente din aceeași poziție grupate împreună (zip()) și înapoi (unzip()). Da, știu, devine dificil să înțelegeți explicația cu cuvinte, așa că haideți să vedem un exemplu de cod:

const animals = ['duck', 'sheep'];
const sizes = ['small', 'large'];
const weight = ['less', 'more'];

const groupedAnimals = _.zip(animals, sizes, weight);
console.log(groupedAnimals);
// [ [ 'duck', 'small', 'less' ], [ 'sheep', 'large', 'more' ] ]

Cele trei matrice originale au fost convertite într-o singură matrice cu doar două matrice. Iar fiecare dintre aceste noi matrice reprezintă un singur animal cu toate informațiile într-un singur loc. Astfel, indexul 0 ne spune tipul de animal, indexul 1 ne spune dimensiunea sa, iar indexul 2 ne spune greutatea sa. Ca rezultat, este mai ușor să lucrați cu datele. După ce ați aplicat orice operațiuni asupra datelor, le puteți separa din nou folosind unzip() și le puteți trimite înapoi la sursa originală:

const animalData = _.unzip(groupedAnimals);
console.log(animalData);
// [ [ 'duck', 'sheep' ], [ 'small', 'large' ], [ 'less', 'more' ] ]

Utilitarul zip/unzip nu este ceva care vă va schimba viața peste noapte, dar o va face într-o zi!

Concluzie 👨‍🏫

(Am pus tot codul sursă folosit în acest articol Aici pentru a-l testa direct în browser!)

Documentația Lodash este plină de exemple și funcții care vă vor uimi. Într-o perioadă în care masochismul pare să crească în ecosistemul JS, Lodash este ca o gură de aer proaspăt și vă recomand cu căldură să folosiți această bibliotecă în proiectele voastre!