Pro vývojáře JavaScriptu není třeba Lodash představovat. Knihovna je však rozlehlá a často působí ohromujícím dojmem. Už ne!
Lodaš, Lodaš, Lodaš. . . kde mám vůbec začít! 🤔
Byly doby, kdy se ekosystém JavaScriptu zrodil; dalo by se to přirovnat k divokému západu nebo chcete-li džungli, kde se toho hodně dělo, ale bylo jen velmi málo odpovědí na každodenní vývojářské frustrace a produktivitu.
Pak Lodash vstoupil na scénu a bylo to jako povodeň, která všechno ponořila. Od jednoduchých každodenních potřeb, jako je třídění až po složité transformace datových struktur, byl Lodash nabitý (dokonce přetížený!) funkcemi, které změnily život vývojářů JS v čiré blaženosti.
Dobrý den, Lodashi!
A kde je Lodash dnes? Pořád má všechny vychytávky, které zpočátku nabízel, a pak ještě nějaké, ale zdá se, že ztratil mysl v komunitě JavaScriptu. Proč? Napadá mě několik důvodů:
- Některé funkce v knihovně Lodash byly (a stále jsou) pomalé při aplikaci na velké seznamy. I když by to nikdy neovlivnilo 95 % projektů tam venku, vlivní vývojáři ze zbývajících 5 % způsobili Lodashovi špatný tisk a efekt se dostal až do řad veřejnosti.
- V ekosystému JS existuje trend (dokonce by se totéž dalo říci o lidech z Golangu), kde je arogance běžnější, než je nutné. Spoléhat se na něco jako Lodash je tedy považováno za hloupé a na fórech, jako je StackOverflow, je sestřeleno, když lidé navrhují taková řešení („Cože?! Použít k něčemu takovému celou knihovnu? Mohu zkombinovat filtr() s redukovat() a dosáhnout totéž v jednoduché funkci!“).
- Lodash je starý. Alespoň podle standardů JS. Vyšlo to v roce 2012, takže od psaní je to skoro deset let. Rozhraní API bylo stabilní a každý rok nelze přidávat mnoho vzrušujících věcí (prostě proto, že to není potřeba), což průměrnému přebuzenému vývojáři JS generuje nudu.
Podle mého názoru je nepoužívání Lodashe významnou ztrátou pro naše kódové základny JavaScriptu. Osvědčila se bez chyb a elegantní řešení pro každodenní problémy, s nimiž se v práci setkáváme, a jeho používání pouze učiní náš kód čitelnějším a udržitelnějším.
Po tom, co bylo řečeno, pojďme se ponořit do některých běžných (nebo ne!) funkcí Lodash a uvidíme, jak neuvěřitelně užitečná a krásná tato knihovna je.
Table of Contents
Klonovat . . . hluboce!
Vzhledem k tomu, že objekty jsou v JavaScriptu předávány odkazem, způsobuje to vývojářům bolest hlavy, když chtějí něco naklonovat s nadějí, že nová datová sada je jiná.
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' } ] */
Všimněte si, jak v naší čisté nevinnosti a navzdory našim dobrým úmyslům původní pole lidí v tomto procesu zmutovalo (Arnoldova specializace se změnila z C++ na JS) – velká rána pro integritu základního softwarového systému! Ve skutečnosti potřebujeme způsob, jak vytvořit věrnou (hlubokou) kopii původního pole.
Ahoj Dave, seznam se s Davem!
Možná můžete namítnout, že je to „hloupý“ způsob kódování v JS; realita je však trochu komplikovaná. Ano, máme k dispozici krásný destrukční operátor, ale každý, kdo se pokusil destruovat složité objekty a pole, zná bolest. Pak je tu myšlenka použít serializaci a de-serializaci (možná JSON) k dosažení hlubokého kopírování, ale váš kód bude pro čtenáře jen složitější.
Naproti tomu se podívejte, jak úžasně elegantní a stručné je řešení, když si Lodash zvykne:
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' } ] */
Všimněte si, jak je pole lidí po hlubokém klonování nedotčené (Arnold se v tomto případě stále specializuje na C++). Ale co je důležitější, kód je srozumitelný.
Odstraňte duplikáty z pole
Odstranění duplikátů z pole zní jako vynikající problém s rozhovorem/tabulí (nezapomeňte, když máte pochybnosti, vrhněte na problém hashmap!). A samozřejmě můžete vždy napsat vlastní funkci, abyste to udělali, ale co když narazíte na několik různých scénářů, ve kterých budou vaše pole jedinečná? Mohli byste pro to napsat několik dalších funkcí (a riskovat, že narazíte na drobné chyby), nebo můžete použít Lodash!
Náš první příklad unikátních polí je poněkud triviální, ale stále představuje rychlost a spolehlivost, kterou Lodash přináší. Představte si, že to uděláte tak, že si veškerou vlastní logiku napíšete sami!
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 ]
Všimněte si, že konečné pole není seřazeno, což zde samozřejmě není žádný problém. Ale nyní si představme složitější scénář: máme řadu uživatelů, které jsme odněkud stáhli, ale chceme se ujistit, že obsahuje pouze jedinečné uživatele. Snadno s 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 } ] */
V tomto příkladu jsme pomocí metody uniqBy() sdělili Lodashovi, že chceme, aby byly objekty jedinečné ve vlastnosti id. V jednom řádku jsme vyjádřili, co by mohlo zabrat 10-20 řádků a zavedli jsme větší prostor pro chyby!
Kolem vytváření jedinečných věcí v Lodash je k dispozici mnohem více věcí a já vám doporučuji, abyste se na ně podívali dokumenty.
Rozdíl dvou polí
Sjednocení, rozdíl atd. by mohly znít jako pojmy, které by bylo nejlepší zapomenout na nudných středoškolských přednáškách Teorie množin, ale v každodenní praxi se objevují častěji než ne. Je běžné, že máte seznam a chcete s ním sloučit další seznam nebo chcete zjistit, které prvky jsou pro něj jedinečné ve srovnání s jiným seznamem; pro tyto scénáře je rozdílová funkce perfektní.
Ahoj, A. Sbohem, B!
Začněme cestu rozdílů pomocí jednoduchého scénáře: obdrželi jste seznam všech ID uživatelů v systému a také seznam těch, jejichž účty jsou aktivní. Jak zjistíte neaktivní ID? Jednoduché, že?
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 když, jak se to stává v realističtějším prostředí, budete muset pracovat s řadou objektů namísto obyčejných primitiv? No, Lodash má na to pěknou metodu RozdílBy()!
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' } ]
Bezvadné, že?!
Stejně jako rozdíl existují v Lodash další metody pro běžné operace s množinami: sjednocení, průnik atd.
Zploštění polí
Potřeba zploštit pole vzniká poměrně často. Jedním případem použití je, že jste obdrželi odpověď API a potřebujete použít kombinaci map() a filter() na složitý seznam vnořených objektů/polí, abyste získali, řekněme, ID uživatelů, a teď vám zbývá pole polí. Zde je úryvek kódu znázorňující tuto situaci:
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);
Uhodnete, jak nyní vypadá postPaidUserIds? Nápověda: je to nechutné!
[ [], [ { userId: 3, date: '2021-08-08', amount: 30, type: 'postpaid' }, { userId: 4, date: '2021-06-06', amount: 330, type: 'postpaid' } ] ]
Nyní, pokud jste rozumný člověk, nechcete psát vlastní logiku, abyste extrahovali objekty objednávky a rozložili je pěkně do řady uvnitř pole. Stačí použít metodu flatten() a vychutnat si hrozny:
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' } ] */
Pamatujte, že flatten() jde pouze do hloubky jedné úrovně. To znamená, že pokud vaše objekty uvíznou dvě, tři nebo více úrovní hluboko, flatten() vás zklamou. V těchto případech má Lodash metodu flattenDeep(), ale mějte na paměti, že použití této metody na velmi velké struktury může věci zpomalit (v zákulisí funguje rekurzivní operace).
Je objekt/pole prázdný?
Díky tomu, jak v JavaScriptu fungují „falešné“ hodnoty a typy, někdy něco tak jednoduchého, jako je kontrola prázdnoty, vyústí v existenciální děs.
Jak zkontrolujete, zda je pole prázdné? Můžete zkontrolovat, zda je jeho délka 0 nebo ne. Jak nyní zkontrolujete, zda je objekt prázdný? No… počkej chvíli! Tady nastává ten nepříjemný pocit a ty příklady JavaScriptu obsahující věci jako [] == false a {} == false začnou kroužit našimi hlavami. Když jste pod tlakem na dodání funkce, nášlapné miny jsou to poslední, co potřebujete – znesnadní pochopení vašeho kódu a zanesou nejistotu do vaší testovací sady.
Práce s chybějícími daty
V reálném světě nás data poslouchají; bez ohledu na to, jak moc to chceme, je to málokdy efektivní a rozumné. Typickým příkladem jsou chybějící objekty/pole null ve velké datové struktuře přijaté jako odpověď API.
Předpokládejme, že jsme jako odpověď API obdrželi následující objekt:
const apiResponse = { id: 33467, paymentRefernce: 'AEE3356T68', // `order` object missing processedAt: `2021-10-10 00:00:00`, };
Jak je ukázáno, obecně dostáváme objekt objednávky v odpovědi z API, ale není tomu tak vždy. Takže, co když máme nějaký kód, který se spoléhá na tento objekt? Jedním ze způsobů by bylo defenzivní kódování, ale v závislosti na tom, jak je objekt objednávky vnořený, bychom brzy začali psát velmi ošklivý kód, pokud bychom se chtěli vyhnout chybám za běhu:
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 ); }
🤢🤢 Jo, velmi ošklivé na psaní, velmi ošklivé na čtení, velmi ošklivé na údržbu a tak dále. Naštěstí má Lodash jednoduchý způsob, jak takové situace řešit.
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
K dispozici je také fantastická možnost poskytnout výchozí hodnotu namísto toho, aby byla pro chybějící věci nedefinována:
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
Nevím jak vám, ale get() je jedna z věcí, které mi vhánějí slzy štěstí do očí. Není to nic okázalého. Neexistuje žádná konzultovaná syntaxe nebo možnosti k zapamatování, přesto se podívejte na množství kolektivního utrpení, které může zmírnit! 😇
Odskakování
V případě, že nejste obeznámeni, debouncing je běžné téma vývoje frontendu. Myšlenka je taková, že někdy je výhodné spustit akci ne okamžitě, ale po nějaké době (obvykle několik milisekund). Co to znamená? Zde je příklad.
Představte si webovou stránku elektronického obchodu s vyhledávacím pruhem (tedy jakýkoli web/webová aplikace v dnešní době!). Pro lepší UX nechceme, aby uživatel musel stisknout Enter (nebo v horším případě stisknout tlačítko „hledat“), aby se mu zobrazily návrhy/náhledy na základě hledaného výrazu. Ale zřejmá odpověď je trochu nabitá: pokud přidáme posluchač události do onChange() pro vyhledávací lištu a spustíme volání API pro každý stisk klávesy, vytvořili bychom pro náš backend noční můru; bylo by příliš mnoho zbytečných volání (pokud se například vyhledá „štětec na bílý koberec“, bude to celkem 18 požadavků!) a téměř všechny budou irelevantní, protože zadání uživatele neskončilo.
Odpověď spočívá v odskoku a myšlenka je tato: neposílejte volání API, jakmile se změní text. Počkejte nějakou dobu (řekněme 200 milisekund) a pokud do té doby dojde k dalšímu stisknutí klávesy, zrušte dřívější počítání času a znovu začněte čekat. Výsledkem je, že pouze když se uživatel zastaví (buď protože přemýšlí, nebo protože je hotový a očekává nějakou odpověď), odešleme požadavek API na backend.
Celková strategie, kterou jsem popsal, je složitá a nebudu se pouštět do synchronizace správy a zrušení časovače; skutečný proces odskoku je však velmi jednoduchý, pokud používáte 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
Pokud si myslíte, že setTimeout() udělal bych stejnou práci, no, je toho víc! Lodashův debounce přichází s mnoha výkonnými funkcemi; můžete například chtít zajistit, aby odskok nebyl neurčitý. To znamená, že i když dojde ke stisknutí klávesy pokaždé, když se funkce chystá spustit (čímž se zruší celý proces), možná se budete chtít ujistit, že volání API bude provedeno i tak po řekněme dvou sekundách. Za tímto účelem má Lodash debounce() možnost maxWait:
const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 150, { maxWait: 2000 }); // debounce for 250ms, but send the API request after 2 seconds anyway
Podívejte se na úředníka dokumenty pro hlubší ponor. Jsou plné superdůležitých věcí!
Odebrat hodnoty z pole
Nevím jak vy, ale já nerad píšu kód pro odstranění položek z pole. Nejprve musím získat index položky, zkontrolovat, zda je index skutečně platný, a pokud ano, zavolat metodu splice() a tak dále. Nikdy si nevzpomenu na syntaxi, a proto musím neustále hledat věci, a na konci jsem zůstal s otravným pocitem, že jsem nechal vplížit nějakou hloupou chybu.
const greetings = ['hello', 'hi', 'hey', 'wave', 'hi']; _.pull(greetings, 'wave', 'hi'); console.log(greetings); // [ 'hello', 'hey' ]
Všimněte si prosím dvou věcí:
Existuje další související metoda nazvaná pullAll(), která přijímá pole jako druhý parametr, což usnadňuje odstranění více položek najednou. Je pravda, že jsme mohli použít pull() s operátorem spreadu, ale nezapomeňte, že Lodash přišel v době, kdy operátor spreadu nebyl ani návrhem v jazyce!
const greetings2 = ['hello', 'hi', 'hey', 'wave', 'hi']; _.pullAll(greetings2, ['wave', 'hi']); console.log(greetings2); // [ 'hello', 'hey' ]
Poslední index prvku
Nativní metoda indexOf() v JavaScriptu je skvělá, kromě případů, kdy vás zajímá skenování pole z opačného směru! A ještě jednou, ano, můžete napsat dekrementační smyčku a najít prvek, ale proč nepoužít mnohem elegantnější techniku?
Zde je rychlé řešení Lodash pomocí metody lastIndexOf():
const integers = [2, 4, 1, 6, -1, 10, 3, -1, 7]; const index = _.lastIndexOf(integers, -1); console.log(index); // 7
Bohužel neexistuje žádná varianta této metody, kde bychom mohli vyhledávat složité objekty nebo dokonce předat vlastní vyhledávací funkci.
zip. Rozepnout!
Pokud jste nepracovali v Pythonu, zip/unzip je nástroj, kterého si za celou svou kariéru vývojáře JavaScriptu nikdy nevšimnete ani si ho nepředstavíte. A možná z dobrého důvodu: jen zřídka existuje taková zoufalá potřeba zip/unzip jako u filter() atd. Nicméně je to jedna z nejlepších méně známých utilit a v některých situacích vám může pomoci vytvořit stručný kód. .
Na rozdíl od toho, jak to zní, zip/unzip nemá nic společného s kompresí. Místo toho je to seskupovací operace, kde lze pole stejné délky převést na jediné pole polí s prvky na stejné pozici sbalené dohromady (zip()) a zpět (unzip()). Jo, já vím, začíná být mlhavé snažit se vystačit se slovy, tak se podívejme na nějaký kód:
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' ] ]
Původní tři pole byla převedena na jedno pouze se dvěma poli. A každé z těchto nových polí představuje jediné zvíře se vším všudy na jednom místě. Index 0 nám tedy říká, o jaký druh zvířete se jedná, index 1 nám říká jeho velikost a index 2 jeho váhu. Díky tomu se s daty nyní lépe pracuje. Jakmile s daty použijete všechny potřebné operace, můžete je znovu rozdělit pomocí unzip() a odeslat je zpět do původního zdroje:
const animalData = _.unzip(groupedAnimals); console.log(animalData); // [ [ 'duck', 'sheep' ], [ 'small', 'large' ], [ 'less', 'more' ] ]
Nástroj zip/unzip není něco, co změní váš život přes noc, ale jednoho dne změní váš život!
Závěr 👨🏫
(Do tohoto článku jsem vložil veškerý zdrojový kód použitý tady můžete vyzkoušet přímo z prohlížeče!)
Lodash dokumenty jsou přeplněné příklady a funkcemi, které vás prostě nadchnou. V době, kdy se zdá, že masochismus v ekosystému JS narůstá, je Lodash jako závan čerstvého vzduchu a velmi doporučuji, abyste tuto knihovnu používali ve svých projektech!