10 важних Лодасх функција за ЈаваСцрипт програмере

Лодаш: Зашто је и даље релевантан за ЈаваСцрипт програмере?

За програмере који користе ЈаваСцрипт, Лодаш није непознаница. Ипак, ова библиотека је изузетно обимна и често може деловати претрпано. Али, не више!

Лодаш, Лодаш, Лодаш… одакле почети? 🤔

Постојало је време када се ЈаваСцрипт екосистем развијао; могао би се упоредити са дивљим западом, или можда џунглом, где се много тога дешавало, али је било врло мало решења за свакодневне изазове и проблеме програмера.

Тада је Лодаш ступио на сцену, и осећао се као бујица која је све преплавила. Од једноставних потреба, попут сортирања, до сложених трансформација података, Лодаш је био препун (можда чак и преоптерећен) функционалности која је живот ЈС програмера учинила чистим задовољством.

Поздрав, Лодаш!

Где је Лодаш данас? Па, још увек има све квалитете које је нудио на почетку, па чак и више од тога, али чини се да је изгубио на популарности у ЈаваСцрипт заједници. Зашто? Могу да се сетим неколико разлога:

  • Неке функције у Лодаш библиотеци су биле (а и даље су) споре при раду са великим листама. Иако ово никада не би утицало на већину пројеката, утицајни програмери из малог процента су дали лош глас Лодашу, а то се проширило.
  • Постоји тренд у ЈС екосистему, где се сматра да је ароганција чешћа него што је потребно. Стога, ослањање на Лодаш се сматра нечим лошим и критикује се на форумима попут СтацкОверфлов-а када људи предлажу таква решења.
  • Лодаш је стар, барем по ЈС стандардима. Појавио се 2012. године, што значи да је прошло скоро десет година од његовог настанка. API је стабилан и не могу се додавати нове узбудљиве ствари сваке године, што доводи до досаде код просечног JS програмера.

По мом мишљењу, не коришћење Лодаша представља значајан губитак за ЈаваСцрипт базе кода. Он пружа проверено, елегантно и безгрешно решење за свакодневне проблеме са којима се суочавамо, а његова употреба ће код учинити читљивијим и лакшим за одржавање.

Са тим на уму, хајде да завиримо у неке од уобичајених (или не!) Лодаш функција и видимо колико је ова библиотека изузетно корисна и добра.

Клонирање… дубоко!

Будући да се објекти у ЈаваСцрипту прослеђују по референци, то ствара проблеме програмерима када желе да клонирају нешто у нади да ће нови скуп података бити другачији.

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

// Пронађи људе који пишу у C++
let folksDoingCpp = people.filter((person) => person.specialization == 'C++');

// Претвори их у 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' }
]
*/

Обратите пажњу како је, упркос нашим добрим намерама, оригинални низ људи промењен током процеса (Арнолдова специјализација је промењена са C++ на JS) — што је озбиљан удар за интегритет софтверског система! Потребан нам је начин да направимо праву (дубоку) копију оригиналног низа.

Можда тврдите да је ово „немарно“ кодирање у JS-у; међутим, стварност је мало компликованија. Да, имамо на располагању оператор деструктурирања, али свако ко је покушао да деструктурира сложене објекте и низове зна каква је то мука. Такође, постоји идеја о коришћењу серијализације и десеријализације (можда ЈСОН) за постизање дубоког копирања, али то само чини код неуредним.

С друге стране, погледајте колико је решење елегантно и концизно када се користи Лодаш:

const _ = require('lodash');

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

let peopleCopy = _.cloneDeep(people);

// Пронађи људе који пишу у C++
let folksDoingCpp = peopleCopy.filter(
  (person) => person.specialization == 'C++'
);

// Претвори их у 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' }
]
*/

Приметили сте како је низ људи остао непромењен након дубоког клонирања (Арнолд је и даље специјализован за C++). Али, што је још важније, код је лако разумљив.

Уклањање дупликата из низа

Уклањање дупликата из низа звучи као одличан проблем за интервјуе (запамтите, ако сте у недоумици, баците хашмап на проблем!). Наравно, увек можете написати прилагођену функцију, али шта ако наиђете на неколико различитих сценарија где низове треба да учините јединственим? Можете написати неколико других функција за то (ризикујући суптилне грешке), или можете једноставно да користите Лодаш!

Наш први пример јединствених низова је прилично једноставан, али ипак показује брзину и поузданост коју Лодаш доноси. Замислите да ово радите сами са прилагођеном логиком!

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 ]

Приметили сте да коначни низ није сортиран, што овде није важно. Сада, замислимо компликованији сценарио: имамо низ корисника које смо преузели одакле, али желимо да будемо сигурни да садржи само јединствене кориснике. Лако уз Лодаш!

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

У овом примеру, користили смо метод uniqBy() да кажемо Лодашу да желимо да објекти буду јединствени на основу својства id. У једном реду смо изразили оно што би могло да заузме 10-20 редова кода и повећа шансу за грешке!

Постоји још много опција за креирање јединствених ствари у Лодашу, и препоручујем да погледате документацију.

Разлика два низа

Унија, разлика, итд., могу звучати као појмови из досадних предавања о теорији скупова, али се чешће појављују у пракси. Уобичајено је да имате листу и желите да спојите другу листу са њом, или да пронађете који елементи су јединствени за једну листу у поређењу са другом. За ове сценарије, функција разлике је савршена.

Хајде да започнемо путовање разлике помоћу једноставног сценарија: добили сте листу свих ИД-ова корисника у систему, као и листу оних чији су налози активни. Како проналазите неактивне ИД-ове? Једноставно, зар не?

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 ]

А шта ако, као што је случај у реалном окружењу, морате радити са низом објеката уместо примитивних типова? Па, Лодаш има одличан 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' } ]

Уредно, зар не?!

Поред разлике, постоје и друге методе у Лодашу за операције са скуповима: унија, пресек, итд.

Равномерни низови

Потреба за изравнавањем низова се често појављује. Један пример употребе је када сте добили одговор од API-ја и морате да примените неку комбинацију map() и filter() на сложену листу угнежђених објеката/низова да бисте добили, рецимо, корисничке ИД-ове, и сада имате низове низова. Ево одломка кода који показује ову ситуацију:

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

// пронађи корисничке ИД-ове који су направили postpaid поруџбине (интерне или екстерне)
const postpaidUserIds = [];

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

Можете ли да погодите како сада изгледа postpaidUserIds? Наговештај: ужасно је!

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

Сада, ако сте разумна особа, не желите да пишете прилагођену логику да бисте издвојили објекте и ставили их у низ. Само користите метод flatten() и уживајте:

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() иде само један ниво дубоко. Ако су ваши објекти заглављени два, три или више нивоа, flatten() ће вас разочарати. У тим случајевима, Лодаш има flattenDeep() метод, али имајте на уму да примена ове методе на великим структурама може успорити ствари (јер се у позадини ради рекурзивна операција).

Да ли је објекат/низ празан?

Због начина на који „лажне“ вредности и типови функционишу у ЈаваСцрипту, понекад нешто тако једноставно као што је провера да ли је нешто празно, доводи до несигурности.

Како проверити да ли је низ празан? Можете проверити да ли је његова дужина 0. Сада, како да проверите да ли је објекат празан? Па… чекајте мало! Овде се појављује тај нелагодан осећај и примери JS-а са стварима попут [] == false и {} == false почињу да нам се мотају по глави. Када сте под притиском да испоручите неку функцију, овакве грешке су последње што вам треба – отежавају разумевање кода и уносе несигурност у ваш тест пакет.

Рад са подацима који недостају

У стварном свету, подаци нису увек у складу са нашим жељама; без обзира колико то желимо, ретко су рационални и разумни. Један типичан пример је недостатак објеката/низова у великим структурама података добијеним као одговор од API-ја.

Претпоставимо да смо добили следећи објекат као одговор API-ја:

const apiResponse = {
  id: 33467,
  paymentRefernce: 'AEE3356T68',
  // недостаје `order` објекат
  processedAt: `2021-10-10 00:00:00`,
};

Као што је приказано, обично добијамо објекат налога у одговору API-ја, али то није увек случај. Шта ако имамо код који зависи од овог објекта? Један од начина би био да кодирамо дефанзивно, али у зависности од тога колико је угнежђен објекат налога, ускоро бисмо писали ружан код ако желимо да избегнемо грешке при извршавању:

if (
  apiResponse.order &&
  apiResponse.order.payee &&
  apiResponse.order.payee.address
) {
  console.log(
    'Поруџбина је послата у поштански број: ' +
      apiResponse.order.payee.address.zipCode
  );
}

🤢🤢 Да, врло ружно за писање, врло ружно за читање, врло ружно за одржавање и тако даље. Срећом, Лодаш има једноставан начин за решавање оваквих ситуација.

const zipCode = _.get(apiResponse, 'order.payee.address.zipCode');
console.log('Поруџбина је послата у поштански број: ' + zipCode);
// Поруџбина је послата у поштански број: undefined

Постоји и фантастична опција да се обезбеди подразумевана вредност за ствари које недостају:

const zipCode2 = _.get(apiResponse, 'order.payee.address.zipCode', 'NA');
console.log('Поруџбина је послата у поштански број: ' + zipCode2);
// Поруџбина је послата у поштански број: NA

Не знам за вас, али get() је једна од оних ствари које ми изазивају сузе радоснице. Није ништа блиставо, не постоје опције које треба запамтити, али погледајте колико колективне патње може да ублажи! 😇

Дебоунсинг

У случају да нисте упознати, дебоунсинг је уобичајена тема у развоју фронтенда. Идеја је да је понекад корисно покренути акцију не одмах, већ након неког времена (обично неколико милисекунди). Шта то значи? Ево примера.

Замислите веб локацију за е-трговину са траком за претрагу (па, било која веб локација/веб апликација данас!). За боље корисничко искуство, не желимо да корисник мора да притисне ентер (или још горе, притисне дугме „претражи“) да би приказао предлоге/прегледе на основу свог упита. Али очигледан одговор има проблем: ако бисмо додали слушалац догађаја на onChange() за траку за претрагу и покренули АПИ позив за сваки притисак на тастер, направили бисмо ноћну мору за наш бекенд систем; било би превише непотребних позива (на пример, ако се претражује „четка за беле тепихе“, биће укупно 18 захтева!) и скоро сви ће бити небитни јер кориснички унос није завршен.

Решење је у дебоунсингу, а идеја је следећа: немојте слати АПИ позив чим се текст промени. Сачекајте неко време (рецимо, 200 милисекунди) и ако до тада дође до још једног притиска на тастер, поништите претходно одбројавање и почните да чекате поново. Као резултат тога, тек када корисник паузира (било зато што размишља или је завршио и очекује одговор), шаљемо АПИ захтев на бекенд.

Цела стратегија коју сам описао је компликована и нећу се бавити синхронизацијом управљања тајмером и отказивања; међутим, сам процес уклањања одскока је веома једноставан ако користите Лодаш.

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

// Ово је прави АПИ за псе!
const fetchDogBreeds = () =>
  axios
    .get('https://dog.ceo/api/breeds/list/all')
    .then((res) => console.log(res.data));

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 1000); // након једне секунде
debouncedFetchDogBreeds(); // приказује податке након неког времена

Ако мислите да би setTimeout() урадио исто, па, има још тога! Лодашев debounce() долази са много моћних функција; на пример, можда желите да се уверите да одбијање није неограничено. Односно, чак и ако постоји притисак на тастер сваки пут када функција треба да се покрене (чиме се поништава цео процес), можда желите да будете сигурни да се АПИ позив ипак пошаље након, рецимо, две секунде. За ово, Лодасх debounce() има опцију maxWait:

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 150, { maxWait: 2000 }); // debounce for 250ms, but send the API request after 2 seconds anyway

Погледајте званичну документацију за детаљнији увид. Пуна је корисних ствари!

Уклоните вредности из низа

Не знам за вас, али ја мрзим да пишем код за уклањање ставки из низа. Прво, морам да добијем индекс ставке, да проверим да ли је индекс заиста валидан и ако јесте, позовем splice() метод и тако даље. Никада не могу да запамтим синтаксу и зато морам стално да тражим ствари, а на крају остајем са мучним осећајем да сам пропустио неку глупу грешку.

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

Имајте на уму две ствари:

  • Оригинални низ се мења током процеса.
  • pull() метод уклања све инстанце, чак и ако постоје дупликати.

Постоји још један сродни метод који се зове pullAll() који прихвата низ као други параметар, што олакшава уклањање више ставки одједном. Наравно, могли бисмо да користимо pull() са оператором ширења, али запамтите да је Лодаш настао у време када оператор ширења није ни постојао у језику!

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

Последњи индекс елемента

ЈаваСцрипт-ов indexOf() метод је одличан, осим када желите да скенирате низ из супротног смера! И опет, да, могли бисте једноставно написати декрементирајућу петљу и пронаћи елемент, али зашто не користити елегантнију технику?

Ево брзог решења са Лодашем користећи метод lastIndexOf():

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

Нажалост, не постоји варијанта овог метода где можемо да тражимо сложене објекте или чак да проследимо прилагођену функцију за претрагу.

Зип. Распакујте!

Осим ако нисте радили у Пајтону, zip/unzip су алати које можда никада нећете приметити током своје ЈаваСцрипт каријере. И можда с разлогом: ретко постоји велика потреба за zip/unzip, као што је то случај са filter(), итд. Међутим, то је један од најбољих мање познатих алата и може вам помоћи да направите сажет код у неким ситуацијама.

Супротно ономе што звучи, zip/unzip немају никакве везе са компресијом. Уместо тога, то је операција груписања где се низови исте дужине могу претворити у један низ низова са елементима на истој позицији спакованим заједно (zip()) и назад (unzip()). Да, знам, постаје нејасно када се покушава описати речима, па хајде да погледамо код:

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

Оригинална три низа су претворена у један са само два низа. И сваки од ових нових низова представља једну животињу са свим садржајима на једном месту. Дакле, индекс 0 нам говори о којој врсти животиње је реч, индекс 1 нам говори о њеној величини, а индекс 2 о њеној тежини. Као резултат тога, сада је лакше радити са подацима. Када примените све операције које су вам потребне на податке, можете их поново разбити користећи unzip() и послати их назад оригиналном извору:

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

zip/unzip није нешто што ће вам променити живот преко ноћи, али ће вам једног дана олакшати живот!

Закључак 👨‍🏫

(Ставио сам сав изворни код који се користи у овом чланку овде, да бисте га испробали директно из претраживача!)

Лодашева документација је пуна примера и функција које ће вас одушевити. У доба када се чини да мазохизам расте у ЈС екосистему, Лодаш је као дашак свежег ваздуха и топло препоручујем да користите ову библиотеку у својим пројектима!