10 важливих функцій Lodash для розробників JavaScript

Для розробників JavaScript Lodash не потребує представлення. Однак бібліотека величезна і часто здається надзвичайною. Більше ні!

Лодаш, Лодаш, Лодаш . . . з чого мені почати! 🤔

Був час, коли екосистема JavaScript лише зароджувалася; його можна порівняти з диким заходом чи джунглями, якщо хочете, де багато чого відбувалося, але було дуже мало відповідей на щоденні розчарування розробників і продуктивність.

Потім Лодаш вийшов на сцену, і це було схоже на повінь, яка затопила все. Від простих повсякденних потреб, таких як сортування, до складних перетворень структури даних, Lodash був завантажений (навіть перевантажений!) функціональністю, яка перетворила життя розробників JS на справжнє блаженство.

Привіт, Лодаш!

А де сьогодні Лодаш? Що ж, у нього все ще є всі переваги, які він пропонував спочатку, а потім і деякі, але, здається, він втратив свою частку в спільноті JavaScript. чому Я можу згадати кілька причин:

  • Деякі функції в бібліотеці Lodash працювали (і залишаються) повільними при застосуванні до великих списків. Хоча це ніколи не вплинуло б на 95% проектів, впливові розробники з решти 5% негативно оцінили Лодаша в пресі, і ефект перекинувся на низову аудиторію.
  • В екосистемі JS існує тенденція (можна навіть сказати те саме про людей Golang), де пиха є більш поширеною, ніж необхідно. Тож покладатися на щось на кшталт Lodash вважається дурницею, і на таких форумах, як StackOverflow, його розбивають, коли люди пропонують такі рішення («Що?! Використовувати цілу бібліотеку для чогось подібного? Я можу поєднати filter() із reduce(), щоб досягти те саме в простій функції!»).
  • Лодаш старий. Принаймні за стандартами JS. Він вийшов у 2012 році, тож на момент написання минуло майже десять років. API був стабільним, і щороку не можна додавати багато захоплюючих речей (просто тому, що в цьому немає потреби), що породжує нудьгу у середнього перезбудженого JS-розробника.

На мою думку, невикористання Lodash є значною втратою для наших кодових баз JavaScript. У ньому є перевірені елегантні рішення без помилок для повсякденних проблем, з якими ми стикаємося на роботі, і його використання лише зробить наш код більш читабельним і зручним для обслуговування.

З огляду на це, давайте зануримося в деякі з поширених (чи ні!) функцій Lodash і побачимо, наскільки ця бібліотека неймовірно корисна та красива.

Клон . . . глибоко!

Оскільки в JavaScript об’єкти передаються за посиланням, це створює головний біль для розробників, коли вони хочуть щось клонувати з надією, що новий набір даних відрізняється.

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

Зверніть увагу, як із нашої чистої невинності та незважаючи на наші добрі наміри вихідний масив людей мутував у процесі (спеціалізація Арнольда змінилася з C++ на JS) — серйозний удар по цілісності базової системи програмного забезпечення! Дійсно, нам потрібен спосіб зробити справжню (глибоку) копію вихідного масиву.

Привіт, Дейв, зустрічай Дейва!

Можливо, ви можете заперечити, що це «дурний» спосіб кодування в JS; однак реальність дещо складніша. Так, у нас є чудовий оператор деструктуризації, але будь-хто, хто намагався деструктурувати складні об’єкти та масиви, знає про це біль. Крім того, існує ідея використання серіалізації та десеріалізації (можливо, JSON) для досягнення глибокого копіювання, але це лише ускладнює ваш код для читача.

І навпаки, подивіться, яке неймовірно елегантне і лаконічне рішення, коли Lodash звикає:

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

Зверніть увагу, як масив людей залишається недоторканим після глибокого клонування (у цьому випадку Арнольд усе ще спеціалізується на C++). Але що важливіше, код простий для розуміння.

  Як збільшити масштаб Nintendo Switch під час гри в будь-яку гру

Видалити дублікати з масиву

Видалення дублікатів із масиву звучить як чудова проблема інтерв’ю/дошки (пам’ятайте, якщо сумніваєтеся, додайте хеш-карту до проблеми!). І, звичайно, ви завжди можете написати спеціальну функцію для цього, але що, якщо ви зіткнетеся з кількома різними сценаріями, у яких ваші масиви будуть унікальними? Ви можете написати кілька інших функцій для цього (і ризикувати зіткнутися з тонкими помилками), або ви можете просто використовувати Lodash!

Наш перший приклад унікальних масивів досить тривіальний, але він все ще демонструє швидкість і надійність, яку привносить Lodash. Уявіть собі, що ви робите це, власноруч створюючи всю спеціальну логіку!

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 ]

Зверніть увагу, що остаточний масив не сортується, що, звісно, ​​тут не має значення. Але тепер давайте уявімо більш складний сценарій: у нас є масив користувачів, який ми звідкись витягли, але ми хочемо переконатися, що він містить лише унікальних користувачів. Легко з 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 }
]
*/

У цьому прикладі ми використали метод uniqBy(), щоб повідомити Lodash, що ми хочемо, щоб об’єкти були унікальними за властивістю id. В одному рядку ми висловили те, що могло б зайняти 10-20 рядків, і представили більше можливостей для помилок!

У Lodash є багато іншого, щоб зробити речі унікальними, і я раджу вам переглянути документи.

Різниця двох масивів

Об’єднання, різниця тощо можуть звучати як терміни, які краще залишити позаду в нудних лекціях із теорії множин у середній школі, але вони частіше спливають у повсякденній практиці. Зазвичай є список і ви хочете об’єднати з ним інший список або бажаєте знайти, які елементи є унікальними для нього порівняно з іншим списком; для цих сценаріїв функція різниці ідеальна.

Привіт, А. До побачення, Б!

Давайте почнемо подорож відмінностей із простого сценарію: ви отримали список усіх ідентифікаторів користувачів у системі, а також список тих, чиї облікові записи активні. Як знайти неактивні ідентифікатори? Просто, правда?

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 ]

А що, якщо, як це буває в більш реалістичному середовищі, вам доведеться працювати з масивом об’єктів замість простих примітивів? Що ж, для цього у Lodash є хороший метод 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' } ]

Гарно, правда?!

Подібно до різниці, у Lodash існують інші методи для операцій із загальними множинами: об’єднання, перетин тощо.

Зведення масивів

Необхідність зведення масивів виникає досить часто. Один із випадків використання полягає в тому, що ви отримали відповідь 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' },
  ],
};

// 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);

Чи можете ви здогадатися, як зараз виглядає 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() вони вас розчарують. У таких випадках Lodash має метод flattenDeep(), але майте на увазі, що застосування цього методу до дуже великих структур може уповільнити роботу (оскільки за лаштунками працює рекурсивна операція).

  Як увімкнути режим лише HTTPS у Mozilla Firefox

Чи порожній об’єкт/масив?

Завдяки тому, як «хибні» значення та типи працюють у JavaScript, інколи щось таке просте, як перевірка на порожнечу, призводить до екзистенціального страху.

Як перевірити, чи порожній масив? Ви можете перевірити, чи дорівнює його довжина 0 чи ні. Тепер, як перевірити, чи об’єкт порожній? Ну… зачекайте! Саме тут виникає відчуття незручності та приклади JavaScript, які містять такі речі, як [] == false і {} == false починають обертатися в наших головах. Коли ви відчуваєте тиск, щоб забезпечити певну функцію, такі протипехотні міни — останнє, що вам потрібно — вони ускладнять розуміння вашого коду та внесуть невизначеність у ваш набір тестів.

Робота з відсутніми даними

У реальному світі дані слухають нас; незалежно від того, як сильно ми цього хочемо, це рідко буває спрощеним і розумним. Типовим прикладом є відсутність нульових об’єктів/масивів у великій структурі даних, отриманій як відповідь API.

Припустімо, ми отримали такий об’єкт як відповідь API:

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

Як показано, зазвичай ми отримуємо об’єкт замовлення у відповідь від API, але це не завжди так. Отже, що, якщо у нас є код, який покладається на цей об’єкт? Одним зі способів було б кодувати захисний код, але залежно від того, наскільки вкладеним є об’єкт порядку, ми скоро будемо писати дуже потворний код, якщо хочемо уникнути помилок під час виконання:

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
  );
}

🤢🤢 Так, дуже негарно писати, дуже негарно читати, дуже негарно підтримувати і так далі. На щастя, Лодаш має простий спосіб вирішення таких ситуацій.

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

Існує також фантастична можливість надати значення за замовчуванням замість того, щоб отримувати значення undefined для відсутніх речей:

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

Я не знаю, як ви, але get() — це одна з тих речей, які викликають у мене сльози щастя. Це не щось кричуще. Немає узгодженого синтаксису чи варіантів для запам’ятовування, але подивіться, скільки колективних страждань це може полегшити! 😇

Відбій

Якщо ви не знайомі, усунення стрибків є загальною темою у розробці інтерфейсу. Ідея полягає в тому, що іноді корисно запускати дію не відразу, а через деякий час (зазвичай, кілька мілісекунд). Що це означає? Ось приклад.

Уявіть веб-сайт електронної комерції з панеллю пошуку (ну, будь-який веб-сайт/веб-програма в наші дні!). Для кращого UX ми не хочемо, щоб користувачеві доводилося натискати Enter (або, що ще гірше, натискати кнопку «пошук»), щоб показати пропозиції/попередній перегляд на основі їхнього пошукового терміну. Але очевидна відповідь дещо завантажена: якщо ми додамо прослуховувач подій до onChange() для панелі пошуку та будемо запускати виклик API для кожного натискання клавіші, ми створимо кошмар для нашої серверної частини; буде надто багато непотрібних викликів (наприклад, якщо буде введено пошук «щітка для білого килима», буде загалом 18 запитів!), і майже всі вони будуть нерелевантними, оскільки введення користувача не завершено.

Відповідь полягає в усуненні стрибків, а ідея така: не надсилайте виклик API, щойно зміниться текст. Зачекайте деякий час (скажімо, 200 мілісекунд), і якщо до цього часу буде ще одне натискання клавіші, скасуйте попередній відлік часу та знову почніть очікування. У результаті лише тоді, коли користувач зупиняється (або тому, що він думає, або тому, що він закінчив і очікує відповіді), ми надсилаємо запит API до серверної частини.

Загальна стратегія, яку я описав, є складною, і я не буду занурюватися в синхронізацію керування таймером і скасування; однак фактичний процес усунення стрибків дуже простий, якщо ви використовуєте 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

Якщо ви думаєте про setTimeout(), я б зробив таку саму роботу, ну, є ще щось! Debounce Лодаша має багато потужних функцій; наприклад, ви можете переконатися, що усунення дребезгу не є невизначеним. Тобто, навіть якщо є натискання клавіші кожного разу, коли функція збирається запускати (таким чином скасовуючи загальний процес), ви можете переконатися, що виклик API все одно виконується через, скажімо, дві секунди. Для цього Lodash debounce() має параметр maxWait:

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

Перевірте офіційного документи для глибшого занурення. Вони повні надзвичайно важливих речей!

  Як виправити, що канал історії Roku не працює

Видалити значення з масиву

Я не знаю як ви, але я ненавиджу писати код для видалення елементів з масиву. По-перше, я маю отримати індекс елемента, перевірити, чи дійсно індекс дійсний, і якщо так, викликати метод splice() і так далі. Я ніколи не можу запам’ятати синтаксис, і тому мені потрібно весь час щось шукати, і в кінці мене залишає неприємне відчуття, що я дозволив якійсь дурній помилці заповзти.

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

Зверніть увагу на дві речі:

  • Оригінальний масив був змінений у процесі.
  • Метод pull() видаляє всі екземпляри, навіть якщо є дублікати.
  • Існує інший пов’язаний метод під назвою pullAll(), який приймає масив як другий параметр, що полегшує видалення кількох елементів одночасно. Зрозуміло, що ми могли просто використовувати pull() з оператором поширення, але пам’ятайте, що Lodash з’явився в той час, коли оператор поширення навіть не був пропозицією в мові!

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

    Останній індекс елемента

    Власний метод JavsScript indexOf() — це круто, за винятком випадків, коли вам потрібно сканувати масив у протилежному напрямку! І знову ж таки, так, ви можете просто написати цикл зменшення та знайти елемент, але чому б не використати набагато більш елегантну техніку?

    Ось швидке рішення Lodash за допомогою методу lastIndexOf():

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

    На жаль, немає варіанту цього методу, за якого ми можемо шукати складні об’єкти або навіть передати спеціальну функцію пошуку.

    Zip. Розпакуйте!

    Якщо ви не працювали на Python, zip/unzip — це утиліта, яку ви можете ніколи не помітити або уявити за всю свою кар’єру розробника JavaScript. І, мабуть, з поважної причини: рідко виникає така відчайдушна потреба в 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 не змінить ваше життя миттєво, але одного разу вона змінить ваше життя!

    Висновок 👨‍🏫

    (Я розмістив увесь вихідний код, використаний у цій статті тут щоб ви спробували прямо з браузера!)

    Лодаш документи переповнені прикладами та функціями, які просто вразять вас. У наш час, коли мазохізм, здається, зростає в екосистемі JS, Lodash — це як ковток свіжого повітря, і я настійно рекомендую вам використовувати цю бібліотеку у своїх проектах!