10 ważnych funkcji Lodash dla programistów JavaScript

Dla programistów JavaScript, Lodash nie wymaga przedstawiania. Jednak biblioteka jest ogromna i często wydaje się przytłaczająca. Nigdy więcej!

Lodasza, Lodasza, Lodasza. . . gdzie mam nawet zacząć! 🤔

Był czas, kiedy ekosystem JavaScript dopiero się rodził; można to porównać do dzikiego zachodu lub dżungli, jeśli wolisz, gdzie dużo się działo, ale było bardzo mało odpowiedzi na codzienne frustracje programistów i produktywność.

Następnie Lodasz wszedł na scenę i poczułem się jak powódź, która zalała wszystko. Od prostych codziennych potrzeb, takich jak sortowanie, po złożone transformacje struktur danych, Lodash był naładowany (nawet przeciążony!) funkcjonalnością, która zmieniła życie programistów JS w czystą błogość.

Witaj Lodziu!

A gdzie jest dzisiaj Lodash? Cóż, nadal ma wszystkie gadżety, które oferował początkowo, a potem trochę, ale wydaje się, że stracił udział w społeczności JavaScript. Czemu? Przychodzi mi do głowy kilka powodów:

  • Niektóre funkcje w bibliotece Lodash były (i nadal są) powolne po zastosowaniu do dużych list. Chociaż nigdy nie wpłynęłoby to na 95% projektów, wpływowi deweloperzy z pozostałych 5% wystawili Lodashowi złą prasę, a efekt rozprzestrzenił się na zwykłych ludzi.
  • Istnieje trend w ekosystemie JS (może nawet powiedzieć to samo o ludziach z Golang), gdzie pycha jest bardziej powszechna niż to konieczne. Tak więc poleganie na czymś takim jak Lodash jest postrzegane jako głupota i jest odrzucane na forach takich jak StackOverflow, gdy ludzie sugerują takie rozwiązania („Co?! Wykorzystaj całą bibliotekę do czegoś takiego? Mogę połączyć filter() z reduce(), aby osiągnąć to samo w prostej funkcji!”).
  • Lodasz jest stary. Przynajmniej według standardów JS. Wyszedł w 2012 roku, więc w chwili pisania minęło już prawie dziesięć lat. Interfejs API jest stabilny i co roku można dodawać niewiele ekscytujących rzeczy (po prostu dlatego, że nie ma takiej potrzeby), co powoduje nudę przeciętnego, nadmiernie podekscytowanego programisty JS.

Moim zdaniem nieużywanie Lodash to znacząca strata dla naszych baz kodu JavaScript. Jest sprawdzonym, pozbawionym błędów i eleganckim rozwiązaniem codziennych problemów, które napotykamy w pracy, a korzystanie z niego sprawi, że nasz kod będzie bardziej czytelny i łatwiejszy w utrzymaniu.

Powiedziawszy to, przyjrzyjmy się niektórym popularnym (lub nie!) funkcjom Lodash i zobaczmy, jak niesamowicie pomocna i piękna jest ta biblioteka.

klon . . . głęboko!

Ponieważ obiekty w JavaScript są przekazywane przez referencję, przysparza to programistom bólu głowy, gdy chcą coś sklonować z nadzieją, że nowy zestaw danych będzie inny.

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

// Find people writing in C++
let folksDoingCpp = people.filter((person) => person.specialization == 'C++');

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

Zauważ, jak w naszej czystej niewinności i pomimo naszych dobrych intencji oryginalna tablica ludzi uległa mutacji (specjalizacja Arnolda zmieniła się z C++ na JS) — poważny cios w integralność podstawowego systemu oprogramowania! Rzeczywiście, potrzebujemy sposobu na wykonanie prawdziwej (głębokiej) kopii oryginalnej tablicy.

Cześć Dave, poznaj Dave’a!

Być może można argumentować, że jest to „głupi” sposób kodowania w JS; jednak rzeczywistość jest nieco skomplikowana. Tak, mamy dostępny piękny operator destrukcyjny, ale każdy, kto próbował zniszczyć złożone obiekty i tablice, zna ból. Następnie pojawia się pomysł wykorzystania serializacji i deserializacji (być może JSON) w celu uzyskania głębokiego kopiowania, ale powoduje to tylko bałagan w kodzie dla czytelnika.

Dla kontrastu, spójrz, jak niesamowicie eleganckie i zwięzłe jest rozwiązanie, gdy Lodash się przyzwyczai:

const _ = require('lodash');

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

let peopleCopy = _.cloneDeep(people);

// Find people writing in C++
let folksDoingCpp = peopleCopy.filter(
  (person) => person.specialization == 'C++'
);

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

Zwróć uwagę, że tablica people pozostaje nietknięta po głębokim klonowaniu (w tym przypadku Arnold nadal specjalizuje się w C++). Ale co ważniejsze, kod jest prosty do zrozumienia.

Usuń duplikaty z tablicy

Usuwanie duplikatów z tablicy brzmi jak doskonały problem z wywiadem / tablicą (pamiętaj, jeśli masz wątpliwości, rzuć mapę mieszania na problem!). I oczywiście zawsze możesz napisać niestandardową funkcję, aby to zrobić, ale co, jeśli napotkasz kilka różnych scenariuszy, w których uczynisz swoje tablice wyjątkowymi? Możesz napisać do tego kilka innych funkcji (i ryzykować subtelne błędy) lub po prostu użyć Lodash!

Nasz pierwszy przykład unikalnych tablic jest raczej trywialny, ale nadal reprezentuje szybkość i niezawodność, które wnosi Lodash. Wyobraź sobie, że robisz to, pisząc całą niestandardową 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 końcowa tablica nie jest posortowana, co oczywiście nie ma tutaj znaczenia. Ale teraz wyobraźmy sobie bardziej skomplikowany scenariusz: mamy tablicę użytkowników, którą skądś ściągnęliśmy, ale chcemy mieć pewność, że zawiera ona tylko unikalnych użytkowników. Spokojnie z Lodashem!

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

W tym przykładzie użyliśmy metody uniqBy(), aby powiedzieć Lodashowi, że chcemy, aby obiekty były unikalne we właściwości id. W jednym wierszu wyraziliśmy to, co mogło zająć 10-20 wierszy i wprowadziliśmy więcej miejsca na błędy!

Jest o wiele więcej dostępnych rzeczy związanych z tworzeniem wyjątkowych rzeczy w Lodash i zachęcam do zapoznania się z tym dokumenty.

Różnica dwóch tablic

Zjednoczenie, różnica itp. mogą brzmieć jak terminy, które najlepiej zostawić na nudnych szkolnych wykładach z teorii mnogości, ale często pojawiają się one w codziennej praktyce. Często zdarza się, że masz listę i chcesz połączyć z nią inną listę lub chcesz znaleźć, które elementy są dla niej unikalne w porównaniu z inną listą; dla tych scenariuszy funkcja różnicowa jest idealna.

Cześć, A. Żegnaj, B!

Rozpocznijmy podróż różnicą od prostego scenariusza: otrzymałeś listę wszystkich identyfikatorów użytkowników w systemie, a także 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, jak to bywa w bardziej realistycznych warunkach, musisz pracować z tablicą obiektów zamiast zwykłych prymitywów? Cóż, Lodash ma do tego fajną metodę differentBy()!

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

Nieźle, prawda?!

Podobnie jak różnica, w Lodash istnieją inne metody dla wspólnych operacji na zbiorach: suma, przecięcie itp.

Spłaszczanie tablic

Potrzeba spłaszczania tablic pojawia się dość często. Jednym z przypadków użycia jest to, że otrzymałeś odpowiedź API i musisz zastosować kombinację map() i filter() na złożonej liście zagnieżdżonych obiektów/tablic, aby wydobyć, powiedzmy, identyfikatory użytkowników, a teraz zostajesz z tablice tablic. Oto fragment kodu przedstawiający tę sytuację:

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

// find user ids that placed postpaid orders (internal or external)
const postpaidUserIds = [];

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

Czy zgadniesz, jak teraz wygląda postPaidUserIds? Podpowiedź: to obrzydliwe!

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

Teraz, jeśli jesteś rozsądną osobą, nie chcesz pisać niestandardowej logiki, aby wyodrębnić obiekty porządku i ładnie ułożyć je w rzędzie wewnątrz tablicy. Po prostu użyj metody flatten() i ciesz się winogronami:

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

Zauważ, że flatten() ma tylko jeden poziom głębokości. Oznacza to, że jeśli twoje obiekty utkną na głębokości dwóch, trzech lub więcej poziomów, flatten() cię rozczarują. W takich przypadkach Lodash ma metodę flattenDeep(), ale należy pamiętać, że zastosowanie tej metody na bardzo dużych strukturach może spowolnić działanie (ponieważ za kulisami działa operacja rekurencyjna).

Czy obiekt/tablica jest pusta?

Dzięki temu, jak „fałszywe” wartości i typy działają w JavaScript, czasami coś tak prostego, jak sprawdzenie pustki, powoduje egzystencjalny lęk.

Jak sprawdzić, czy tablica jest pusta? Możesz sprawdzić, czy jego długość wynosi 0, czy nie. Jak teraz sprawdzić, czy obiekt jest pusty? Cóż… poczekaj chwilę! W tym miejscu pojawia się to niespokojne uczucie i te przykłady JavaScript zawierające takie rzeczy [] == fałsz i {} == falstart krąży nam po głowach. Kiedy jesteś pod presją dostarczenia funkcji, takie miny lądowe są ostatnią rzeczą, jakiej potrzebujesz — sprawią, że twój kod będzie trudny do zrozumienia i wprowadzą niepewność do twojego zestawu testów.

Praca z brakującymi danymi

W prawdziwym świecie dane nas słuchają; bez względu na to, jak bardzo tego chcemy, rzadko jest to usprawnione i rozsądne. Typowym przykładem jest brak pustych obiektów/tablic w dużej strukturze danych otrzymanej jako odpowiedź API.

Załóżmy, że otrzymaliśmy następujący obiekt jako odpowiedź API:

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

Jak pokazano, zwykle otrzymujemy obiekt zamówienia w odpowiedzi z interfejsu API, ale nie zawsze tak jest. A co jeśli mamy jakiś kod, który opiera się na tym obiekcie? Jednym ze sposobów byłoby kodowanie defensywne, ale w zależności od tego, jak zagnieżdżony jest obiekt zamówienia, wkrótce byśmy napisali bardzo brzydki kod, jeśli chcemy uniknąć błędów w czasie wykonywania:

if (
  apiResponse.order &&
  apiResponse.order.payee &&
  apiResponse.order.payee.address
) {
  console.log(
    'The order was sent to the zip code: ' +
      apiResponse.order.payee.address.zipCode
  );
}

🤢🤢 Tak, bardzo brzydkie w pisaniu, bardzo brzydkie w czytaniu, bardzo brzydkie w utrzymaniu i tak dalej. Na szczęście Lodash ma prosty sposób radzenia sobie z takimi sytuacjami.

const zipCode = _.get(apiResponse, 'order.payee.address.zipCode');
console.log('The order was sent to the zip code: ' + zipCode);
// The order was sent to the zip code: undefined

Istnieje również fantastyczna opcja podania wartości domyślnej zamiast niezdefiniowania brakujących elementów:

const zipCode2 = _.get(apiResponse, 'order.payee.address.zipCode', 'NA');
console.log('The order was sent to the zip code: ' + zipCode2);
// The order was sent to the zip code: NA

Nie wiem jak ty, ale get() to jedna z tych rzeczy, które wywołują łzy szczęścia w moich oczach. To nie jest nic błyskotliwego. Nie ma konsultowanej składni ani opcji do zapamiętania, ale spójrz na ilość zbiorowego cierpienia, które może złagodzić! 😇

Odbicie

Jeśli nie jesteś zaznajomiony, odrzucanie jest częstym tematem w programowaniu frontendu. Chodzi o to, że czasami korzystne jest rozpoczęcie akcji nie od razu, ale po pewnym czasie (zwykle kilka milisekund). Co to znaczy? Oto przykład.

Wyobraź sobie witrynę e-commerce z paskiem wyszukiwania (w dzisiejszych czasach każda witryna/aplikacja internetowa!). Aby uzyskać lepszy UX, nie chcemy, aby użytkownik musiał naciskać Enter (lub, co gorsza, przycisk „szukaj”), aby wyświetlać sugestie/podglądy na podstawie wyszukiwanego hasła. Ale oczywista odpowiedź jest nieco obciążona: jeśli dodamy detektor zdarzeń do onChange() dla paska wyszukiwania i uruchomimy wywołanie API dla każdego naciśnięcia klawisza, stworzymy koszmar dla naszego zaplecza; byłoby zbyt wiele niepotrzebnych wywołań (na przykład, jeśli wyszukasz „szczotkę do białych dywanów”, pojawi się w sumie 18 żądań!) i prawie wszystkie z nich będą nieistotne, ponieważ wprowadzanie danych przez użytkownika nie zostało zakończone.

Odpowiedź leży w debouncingu, a pomysł jest następujący: nie wysyłaj wywołania API, gdy tylko tekst się zmieni. Poczekaj jakiś czas (powiedzmy 200 milisekund), a jeśli do tego czasu nastąpi kolejne naciśnięcie klawisza, anuluj wcześniejsze odliczanie czasu i ponownie zacznij czekać. W rezultacie tylko wtedy, gdy użytkownik zatrzymuje się (ponieważ myśli lub dlatego, że skończył i oczekuje jakiejś odpowiedzi), wysyłamy żądanie API do zaplecza.

Ogólna strategia, którą opisałem, jest skomplikowana i nie będę zagłębiał się w synchronizację zarządzania i anulowania timera; jednak rzeczywisty proces odrzucania jest bardzo prosty, jeśli używasz Lodash.

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

// This is a real dogs' API, by the way!
const fetchDogBreeds = () =>
  axios
    .get('https://dog.ceo/api/breeds/list/all')
    .then((res) => console.log(res.data));

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 1000); // after one second
debouncedFetchDogBreeds(); // shows data after some time

Jeśli myślisz o setTimeout() Wykonałbym tę samą pracę, cóż, jest więcej! Debounce Lodasha ma wiele zaawansowanych funkcji; na przykład możesz chcieć upewnić się, że odrzucenie nie jest nieokreślone. Oznacza to, że nawet jeśli za każdym razem, gdy funkcja ma zostać uruchomiona, następuje naciśnięcie klawisza (w ten sposób anulowanie całego procesu), możesz chcieć upewnić się, że 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 }); // debounce for 250ms, but send the API request after 2 seconds anyway

Sprawdź urzędnika dokumenty na głębsze nurkowanie. Są pełne super ważnych rzeczy!

Usuń wartości z tablicy

Nie wiem jak ty, ale ja nienawidzę pisać kodu do usuwania elementów z tablicy. Najpierw muszę uzyskać indeks elementu, sprawdzić, czy indeks jest rzeczywiście ważny, a jeśli tak, wywołać metodę splice() i tak dalej. Nigdy nie pamiętam składni i dlatego cały czas muszę sprawdzać, a na koniec mam dokuczliwe uczucie, że pozwoliłem wkraść się jakiemuś głupiemu robakowi.

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

Proszę zwrócić uwagę na dwie rzeczy:

  • Oryginalna tablica została zmieniona w trakcie.
  • Metoda pull() usuwa wszystkie instancje, nawet jeśli występują duplikaty.
  • Istnieje inna pokrewna metoda o nazwie pullAll(), która akceptuje tablicę jako drugi parametr, co ułatwia usuwanie wielu elementów jednocześnie. To prawda, że ​​moglibyśmy po prostu użyć pull() z operatorem spread, ale pamiętaj, że Lodash pojawił się w czasie, gdy operator spread nie był nawet propozycją w języku!

    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, z wyjątkiem sytuacji, gdy chcesz przeskanować tablicę z przeciwnego kierunku! I znowu, tak, możesz po prostu napisać pętlę dekrementacyjną i znaleźć element, ale dlaczego nie użyć o wiele bardziej eleganckiej techniki?

    Oto szybkie rozwiązanie Lodash przy użyciu 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, w którym moglibyśmy wyszukiwać złożone obiekty, a nawet przekazywać niestandardową funkcję wyszukiwania.

    Zamek błyskawiczny. Rozsunąć suwak!

    Jeśli nie pracowałeś w Pythonie, zip/unzip to narzędzie, którego możesz nigdy nie zauważyć lub wyobrazić sobie w całej swojej karierze programisty JavaScript. I być może nie bez powodu: rzadko zdarza się tak desperacka potrzeba zip/unzip, jak filter() itp. Jest to jednak jedno z najlepszych mniej znanych narzędzi, które może pomóc w tworzeniu zwięzłego kodu w niektórych sytuacjach .

    Wbrew temu, co mogłoby się wydawać, zip/unzip nie ma nic wspólnego z kompresją. Zamiast tego jest to operacja grupowania, w której tablice o tej samej długości można przekształcić w pojedynczą tablicę tablic z elementami w tej samej pozycji spakowanymi razem (zip()) i spowrotem (unzip()). Tak, wiem, robi się niejasno, próbując zadowolić się słowami, więc spójrzmy 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' ] ]

    Oryginalne trzy tablice zostały przekonwertowane na jedną z tylko dwiema tablicami. A każda z tych nowych tablic reprezentuje pojedyncze zwierzę ze wszystkimi w jednym miejscu. Tak więc indeks 0 mówi nam, jaki to rodzaj zwierzęcia, indeks 1 mówi nam o jego wielkości, a indeks 2 mówi nam o jego wadze. W rezultacie praca z danymi jest teraz łatwiejsza. Po zastosowaniu wszelkich potrzebnych operacji na danych możesz je ponownie rozbić za pomocą unzip() i odesłać z powrotem do pierwotnego źródła:

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

    Narzędzie zip/unzip nie jest czymś, co zmieni twoje życie z dnia na dzień, ale zmieni je pewnego dnia!

    Wniosek 👨‍🏫

    (Umieszczam cały kod źródłowy użyty w tym artykule tutaj aby spróbować bezpośrednio z przeglądarki!)

    Lodasz dokumenty są pełne przykładów i funkcji, które po prostu zadziwią Cię. W czasach, gdy masochizm wydaje się narastać w ekosystemie JS, Lodash jest jak powiew świeżego powietrza i gorąco polecam korzystanie z tej biblioteki w swoich projektach!