10 ważnych funkcji Lodash dla programistów JavaScript

Photo of author

By maciekx

Biblioteka Lodash, wśród programistów JavaScript, jest powszechnie znana. Jest jednak tak obszerna, że często bywa odbierana jako przytłaczająca. Od teraz to się zmieni!

Lodash, Lodash, Lodash… gdzie właściwie zacząć? 🤔

W początkach rozwoju ekosystemu JavaScript panował chaos. Można to porównać do dzikiego zachodu, lub dżungli, gdzie działo się wiele, ale brakowało gotowych rozwiązań na codzienne trudności i problemy programistów, które wpływały na ich produktywność.

Wtedy pojawił się Lodash, niczym zbawienna powódź. Od prostych, codziennych potrzeb, jak sortowanie, po złożone przekształcenia struktur danych, Lodash dostarczył (a nawet przeciążył!) bogactwo funkcjonalności, która zamieniła życie programistów JS w komfortową rzeczywistość.

Witaj Lodash!

Jak ma się Lodash obecnie? Oferuje wciąż wszystkie swoje początkowe narzędzia, a nawet więcej, ale wydaje się, że stracił na popularności w społeczności JavaScript. Dlaczego? Przychodzi na myśl kilka powodów:

  • Niektóre funkcje biblioteki Lodash działały (i nadal działają) powoli, gdy zastosowano je do dużych zbiorów danych. Chociaż nie miało to znaczenia dla większości projektów, wpływowi twórcy z pozostałych 5% przypadków przyczynili się do negatywnej opinii o Lodash, która rozprzestrzeniła się na pozostałych programistów.
  • W ekosystemie JS (a także w społeczności Golang) panuje tendencja do większego niż potrzebne zadufania. Zatem korzystanie z biblioteki takiej jak Lodash jest postrzegane jako wyraz ignorancji, odrzucane na forach typu StackOverflow, gdy ktoś sugeruje takie rozwiązanie. „Jak to?! Użyjesz całej biblioteki do czegoś takiego? Mogę to samo osiągnąć używając filter() z reduce() w prostej funkcji!”
  • Lodash jest już starszy, przynajmniej w świecie JS. Zadebiutował w 2012 roku, czyli w chwili pisania tego tekstu minęło już prawie dziesięć lat. API jest stabilne i każdego roku rzadko dodawane są ekscytujące nowości (głównie dlatego, że nie ma takiej potrzeby), co nudzi przeciętnego, zbyt podekscytowanego programistę JS.

Uważam, że nieużywanie Lodash to strata dla naszych projektów JavaScript. To sprawdzone, wolne od błędów, eleganckie rozwiązanie codziennych problemów, które spotykamy w pracy. Używanie go poprawia czytelność i ułatwia utrzymanie kodu.

Dlatego przeanalizujmy kilka popularnych (i tych mniej znanych!) funkcji Lodash, aby zobaczyć, jak bardzo pomocna i elegancka jest ta biblioteka.

Klonowanie… głębokie!

Ponieważ obiekty w JavaScript są przekazywane przez referencje, programiści napotykają trudności, gdy chcą coś sklonować, mając nadzieję na nowy, niezależny zestaw danych.

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

// Znajdź osoby piszące w C++
let folksDoingCpp = people.filter((person) => person.specialization == 'C++');

// Zamień ich na 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' }
]
*/

Zwróć uwagę, że oryginalna tablica 'people’ została nieumyślnie zmodyfikowana (specjalizacja Arnolda została zmieniona z C++ na JS) – to poważny cios dla integralności systemu! Potrzebujemy sposobu na stworzenie prawdziwej (głębokiej) kopii pierwotnej tablicy.

Cześć Dave, poznaj Dave’a!

Można argumentować, że jest to „głupi” sposób kodowania w JS, ale rzeczywistość bywa bardziej skomplikowana. Mamy dostępny operator destrukcyjny, ale każdy, kto próbował skopiować złożone obiekty i tablice zna jego ograniczenia. Kolejnym pomysłem jest serializacja i deserializacja (np. JSON), ale tylko komplikuje to kod.

W kontraście, zobacz, jak proste jest rozwiązanie z Lodash:

const _ = require('lodash');

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

let peopleCopy = _.cloneDeep(people);

// Znajdź osoby piszące w C++
let folksDoingCpp = peopleCopy.filter(
  (person) => person.specialization == 'C++'
);

// Zamień ich na 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' }
]
*/

Zauważ, że tablica 'people’ pozostała nienaruszona po głębokim klonowaniu (Arnold nadal specjalizuje się w C++). A co najważniejsze, kod jest łatwy do zrozumienia.

Usuwanie duplikatów z tablicy

Usuwanie duplikatów z tablicy to idealny problem na rozmowę rekrutacyjną. Zawsze możesz napisać niestandardową funkcję, ale co jeśli będziesz potrzebować tej operacji w różnych scenariuszach? Możesz napisać kilka dodatkowych funkcji (ryzykując ukryte błędy) lub po prostu użyć Lodash!

Nasz pierwszy przykład jest prosty, ale pokazuje szybkość i niezawodność Lodash. Wyobraź sobie, że piszesz całą tą logikę samodzielnie!

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 ]

Zauważ, że tablica wynikowa nie jest posortowana. Ale wyobraźmy sobie bardziej skomplikowany przypadek: mamy tablicę użytkowników i chcemy mieć pewność, że zawiera ona tylko unikalnych użytkowników. Lodash przychodzi z pomocą!

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 }
]
*/

Używamy metody 'uniqBy()’, aby wskazać, że obiekty mają być unikalne ze względu na właściwość 'id’. W jednej linii wyrażamy coś, co mogłoby zająć 10-20 linii i wprowadzić więcej potencjalnych błędów!

Lodash oferuje więcej metod związanych z tworzeniem unikalnych zbiorów. Zachęcam do zapoznania się z dokumentacją.

Różnica dwóch tablic

Suma, różnica, iloczyn zbiorów… brzmi jak nudny wykład z teorii mnogości, ale często spotykamy je w praktyce. Chcemy połączyć listę z inną lub znaleźć elementy unikalne w porównaniu z inną listą. W takich przypadkach funkcja różnicy jest idealna.

Cześć, A. Żegnaj, B!

Zacznijmy od prostego scenariusza: masz listę wszystkich identyfikatorów użytkowników w systemie oraz listę tych, których konta są aktywne. Jak znaleźć nieaktywne identyfikatory? Proste, prawda?

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 ]

A co jeśli, w bardziej realistycznych warunkach, pracujesz z tablicą obiektów zamiast prostych danych? Lodash ma do tego metodę 'differenceBy()’.

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' } ]

Całkiem niezłe, prawda?!

Podobnie jak 'difference’, Lodash zawiera inne metody dla typowych operacji na zbiorach: suma, iloczyn, itd.

Spłaszczanie tablic

Potrzeba spłaszczania tablic pojawia się dość często. Otrzymujesz odpowiedź API, musisz zastosować kombinację map() i filter() na zagnieżdżonej liście obiektów/tablic, aby wydobyć identyfikatory użytkowników, a na koniec masz tablicę tablic. Oto przykładowy kod:

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' },
  ],
};

// Znajdź identyfikatory użytkowników, którzy złożyli zamówienia 'postpaid' (wewnętrzne i zewnętrzne)
const postpaidUserIds = [];

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

Jak teraz wygląda 'postPaidUserIds’? Podpowiedź: jest okropne!

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

Nie chcesz pisać niestandardowej logiki, aby wyodrębnić obiekty i umieścić je w jednej tablicy. Użyj metody 'flatten()’ i ciesz się prostotą:

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' }
]
*/

’flatten()’ działa tylko na jednym poziomie zagnieżdżenia. Jeśli obiekty są głębiej, metoda 'flatten()’ Cię rozczaruje. W takich przypadkach Lodash ma metodę 'flattenDeep()’, ale pamiętaj, że użycie jej na bardzo dużych strukturach może spowolnić działanie (działa rekurencyjnie).

Czy obiekt/tablica jest pusta?

Przez to, jak działają „fałszywe” wartości i typy w JavaScript, proste sprawdzenie, czy coś jest puste, potrafi przysporzyć problemów.

Jak sprawdzić, czy tablica jest pusta? Możesz sprawdzić, czy jej długość wynosi 0. Jak sprawdzić, czy obiekt jest pusty? …Chwila! Wtedy pojawia się niepokojące uczucie, a przykłady JavaScript typu [] == false i {} == false krążą nam po głowach. Takie pułapki w stresie mogą utrudnić zrozumienie kodu i wprowadzić niepewność do testów.

Praca z brakującymi danymi

W prawdziwym świecie dane nie zawsze są idealne. Częstym problemem są brakujące puste obiekty/tablice w dużej strukturze danych otrzymanej jako odpowiedź API.

Załóżmy, że otrzymaliśmy taki obiekt jako odpowiedź API:

const apiResponse = {
  id: 33467,
  paymentRefernce: 'AEE3356T68',
  // Brak obiektu `order`
  processedAt: `2021-10-10 00:00:00`,
};

Zwykle otrzymujemy obiekt 'order’ w odpowiedzi z API, ale nie zawsze tak jest. Co jeśli kod polega na tym obiekcie? Jednym ze sposobów jest defensywne kodowanie, ale w zależności od stopnia zagnieżdżenia obiektu 'order’, napisanie kodu bez błędów szybko stałoby się bardzo brzydkie:

if (
  apiResponse.order &&
  apiResponse.order.payee &&
  apiResponse.order.payee.address
) {
  console.log(
    'Zamówienie zostało wysłane na kod pocztowy: ' +
      apiResponse.order.payee.address.zipCode
  );
}

🤢🤢 Tak, brzydko się pisze, brzydko się czyta, brzydko się utrzymuje. Na szczęście Lodash ma prosty sposób na radzenie sobie z takimi sytuacjami.

const zipCode = _.get(apiResponse, 'order.payee.address.zipCode');
console.log('Zamówienie zostało wysłane na kod pocztowy: ' + zipCode);
// Zamówienie zostało wysłane na kod pocztowy: undefined

Możemy też podać wartość domyślną zamiast 'undefined’:

const zipCode2 = _.get(apiResponse, 'order.payee.address.zipCode', 'NA');
console.log('Zamówienie zostało wysłane na kod pocztowy: ' + zipCode2);
// Zamówienie zostało wysłane na kod pocztowy: NA

’get()’ to jedna z tych funkcji, która wywołuje u mnie łzy szczęścia. Nie ma w niej niczego błyskotliwego, ale zobacz ile problemów potrafi rozwiązać!

Debounce

Debounce to popularny temat w programowaniu frontend. Chodzi o to, że czasami opóźnienie wykonania akcji jest korzystne. Oto przykład:

Wyobraź sobie stronę e-commerce z paskiem wyszukiwania. Aby poprawić UX, nie chcemy, aby użytkownik musiał klikać Enter, aby zobaczyć sugestie. Ale jeśli dołączymy detektor zdarzeń 'onChange()’ dla paska wyszukiwania i uruchomimy wywołanie API dla każdego naciśnięcia klawisza, stworzymy koszmar dla zaplecza. Byłoby zbyt wiele niepotrzebnych wywołań (np. wyszukując „szczotka do białych dywanów” pojawiłoby się 18 zapytań). Większość z nich byłaby bezcelowa, bo wpisywanie nie zostało zakończone.

Rozwiązaniem jest debounce: nie wysyłaj wywołania API za każdym razem, gdy tekst się zmienia. Odczekaj chwilę (np. 200 milisekund). Jeśli do tego czasu nastąpi kolejne naciśnięcie klawisza, anuluj poprzednie odliczanie i zacznij czekać od nowa. Tylko wtedy, gdy użytkownik przestanie pisać, wysyłamy żądanie API.

Opisana strategia jest złożona, nie będę zagłębiał się w synchronizację zarządzania i anulowania licznika. Sam proces debounce jest bardzo prosty, jeśli używasz Lodash.

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

// To prawdziwe API dla ras psów!
const fetchDogBreeds = () =>
  axios
    .get('https://dog.ceo/api/breeds/list/all')
    .then((res) => console.log(res.data));

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 1000); // po sekundzie
debouncedFetchDogBreeds(); // pokazuje dane po czasie

Jeśli myślisz, że 'setTimeout()’ zrobi to samo, to mylisz się. Debounce w Lodash ma wiele zaawansowanych funkcji. Możesz chcieć zapewnić, że opóźnienie nie jest nieokreślone. Oznacza to, że nawet jeśli wciąż pojawiają się nowe naciśnięcia klawiszy, wywołanie API i tak zostanie wykonane po, powiedzmy, dwóch sekundach. W tym celu Lodash 'debounce()’ ma opcję 'maxWait’:

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 150, { maxWait: 2000 }); // opóźnienie na 150ms, ale wyślij zapytanie po 2 sekundach

Sprawdź dokumentację, aby dowiedzieć się więcej.

Usuwanie wartości z tablicy

Nie lubię pisać kodu do usuwania elementów z tablicy. Muszę najpierw zdobyć indeks elementu, sprawdzić, czy indeks jest poprawny i dopiero potem wywołać metodę 'splice()’. Nigdy nie pamiętam składni i na koniec mam uczucie, że gdzieś wkradł się jakiś głupi błąd.

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

Zwróć uwagę na dwie rzeczy:

  • Oryginalna tablica została zmodyfikowana.
  • Metoda 'pull()’ usuwa wszystkie instancje, nawet duplikaty.
  • Istnieje inna metoda 'pullAll()’, która akceptuje tablicę jako drugi parametr, ułatwiając usuwanie wielu elementów jednocześnie. Możemy po prostu użyć 'pull()’ z operatorem spread, ale pamiętajmy, że Lodash powstał w czasach, gdy operator spread nie istniał!

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

    Ostatni indeks elementu

    Natywna metoda 'indexOf()’ języka JavaScript jest fajna, ale nie gdy chcesz przeszukać tablicę od końca! Możesz napisać pętlę dekrementacyjną i znaleźć element, ale dlaczego nie użyć bardziej eleganckiej techniki?

    Oto szybkie rozwiązanie Lodash za pomocą metody 'lastIndexOf()’:

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

    Niestety, nie ma wariantu tej metody, gdzie możemy wyszukiwać złożone obiekty lub przekazać niestandardową funkcję wyszukiwania.

    Zip. Rozpakuj!

    Jeśli nie pracowałeś w Pythonie, 'zip’/’unzip’ to narzędzie, które możesz przeoczyć w swojej karierze programisty JavaScript. I być może nie bez powodu: rzadko potrzebujemy 'zip’/’unzip’, jak na przykład 'filter()’. Jest to jednak jedno z najlepszych mniej znanych narzędzi, które w pewnych sytuacjach pozwala pisać bardziej zwięzły kod.

    Wbrew pozorom, 'zip’/’unzip’ nie mają nic wspólnego z kompresją. Jest to operacja grupowania. Tablice o tej samej długości można przekształcić w jedną tablicę tablic, gdzie elementy na tej samej pozycji są spakowane razem (’zip()’) i rozpakowane z powrotem (’unzip()’). Wiem, że brzmi to niejasno, więc spójrz na kod:

    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' ] ]

    Trzy oryginalne tablice zostały zamienione na jedną tablicę z dwiema tablicami. Każda z tych nowych tablic reprezentuje pojedyncze zwierzę. Indeks 0 mówi nam jaki to gatunek, indeks 1 o jego wielkości, a indeks 2 o wadze. Łatwiej teraz pracować z tymi danymi. Po wykonaniu operacji możesz rozpakować je z powrotem za pomocą 'unzip()’:

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

    ’zip’/’unzip’ to nie są narzędzia, które całkowicie odmienią Twoje życie, ale mogą Ci się przydać pewnego dnia!

    Podsumowanie 👨‍🏫

    (Cały kod z tego artykułu jest dostępny tutaj, możesz go wypróbować bezpośrednio w przeglądarce!)

    Dokumentacja Lodash jest pełna przykładów i funkcji, które Cię zaskoczą. W czasach, gdy w ekosystemie JS wydaje się narastać masochizm, Lodash jest jak powiew świeżego powietrza. Polecam Ci używać tej biblioteki w swoich projektach!


    newsblog.pl