- PVSM.RU - https://www.pvsm.ru -

Чем заменить Lodash. Реальные примеры

Всем, привет. Меня зовут Виталий Киреев и я руковожу исследованиями и разработкой в IT-компании. Компания существует уже много лет и в разработке накопилось довольно много Legacy-кода. Мы регулярно проводим аудит на предмет использования устаревших библиотек и меняем их, если в этом есть необходимость. В этой статье я расскажу о практических кейсах, с которыми мы столкнулись при замене широко известной библиотеки Lodash [1] для Javascript.

Зачем менять Lodash?

Сразу оговорюсь, что на Хабре есть очень хорошая статья на эту тему "Вам не нужен Lodash" [2]. Очень рекомендую её прочитать и не буду писать спойлеры, скажу только, что для нас довольно критичными стали следующие моменты:

  • отсутствие обновлений библиотеки с 2021 года (v4.17.21). Сам релиз v4 от 2016 года;

  • критические уязвимости безопасности при проверке OWASP Dependency check.

Есть ещё несколько некритичных моментов, связанных с неочевидной работой некоторых методов и увеличением зависимостей в проекте.

Не смотря но то, что статья очень хорошо и подробно описывает минусы использования этой библиотеки, в ней совсем вскользь упоминаются практические советы, как и на что менять Lodash в различных кейсах. Именно этот пробел я и попробую закрыть в этой статье, а помог мне в этом Александр Долженко, наш frontend разработчик.

Кейсы использования Lodash

Сама библиотека довольно большая и кейсов, в которых её используют тоже много, но здесь я бы хотел обсудить те из них, с которыми мы столкнулись в нашем проекте Личного Кабинета пользователя на React.js. Вероятно у вас будут и другие варианты использования, но, надеюсь, что наши советы вам тоже будут полезны. Итак, после аудита, нашего проекта мы определили кейсы, в которых использовался Lodash:

  • cloneDeep (клонированиe объектов);

  • isEmpty (проверка на пустое значение);

  • isEqual (проверка на эквивалентность двух объектов);

  • every (проверка всех элементов коллекции на соответствие условию);

  • get (безопасное извлечение свойства из объекта);

  • round (округление).

Замена методов Lodash на нативные методы Javascript

Начнем с того, что многие методы библиотеки можно заменить на нативные, и наши не исключение.

cloneDeep() -> structuredClone()

Для большинства случаев копирования нативный structureClone прекрасно справится, но нужно учитывать следующие особенности:

  • при попытке склонировать функции получим ошибку;

  • не получится склонировать DOM-элементы;

  • прототипы при клонировании потеряются.

В нашем проекте таких кейсов не было, поэтому замена прошла гладко.

every() -> Object.values().every()

Мы использовали проверки соответствия элементов коллекции условиям, причем часто это были, например, коллекции валидаторов, которые было удобно проверять таким образом

import _every from "lodash/every";
// в коллекции isValid валидаторы проверки данных
if (_every(isValid, Boolean)) {
  // все данные валидны
}

в таком случае мы заменяем на нативный метод every, который применяем для значений коллекции:

// в коллекции isValid валидаторы проверки данных
if (Object.values(isValid).every(Boolean)) {
  // все данные валидны
}

get() -> опциональные операторы (?.) и nullish coalescing (??)

Действительно, безопасное получение значения свойства возможна и нативными средствами Javascript. Например, пытаемся получить свойство key из values, и если его нет возвращаем пустую строку

import get from "lodash/get";

export const replaceData = (string = "", values = {}) => {
    return string.replace(/{{s*(.*?)s*}}/g, (s, key) =>
        get(values, key, ""),
    );
};

заменим на опциональный оператор и nullish coalescing

export const replaceData = (string = "", values = {}) => {
    return string.replace(/{{s*(.*?)s*}}/g, (s, key) =>
        values?.[key] ?? "",
    );
};

round() -> Math.round()

Здесь без комментариев, как и в примерах работы с массивами из оригинальной статьи о бесполезности Lodash, нет никакого смысла увеличивать число зависимостей, если можно этого не делать.

Замена методов Lodash собственными методами

Это наиболее спорная часть рефакторинга, т.к. кто-то может решить использовать сторонние библиотеки, вместо написания собственного кода, и он имеет на это полное право. Для наших случаев использования командой было принято решение написать собственные реализации, которые я бы хотел показать, как пример решения проблемы.

isEmpty()

Например, в нашей frontend части строка из одних пробелов также является пустой, поэтому реализация метода получилась такой:

export const isEmpty = (value) => {
    return (
        value === undefined ||
        value === null ||
        (typeof value === "object" && Object.keys(value).length === 0) ||
        (typeof value === "string" && value.trim().length === 0) 
        // уберите trim, если строка из одних пробелов для вас не пустая
    );
};

isEqual()

При проверке на эквивалентность двух объектов мы учитываем нюансы сравнения типов Date, Array, Object:

export const isEqual = (a, b) => {
    if (typeof a !== "object" && typeof b !== "object") {
        return Object.is(a, b);
    }
    // Хотя null — примитивный тип в JavaScript, из-за некоторых 
    // исторических особенностей тип null — object, поэтому нам требуется 
    // дополнительная обработка для null.
    if (a === null && b === null) {
        return true;
    }
    if (typeof a !== typeof b) {
        return false;
    }
    // Сначала пробуем строгое сравнение.
    if (a === b) {
        return true;
    }
    // Отдельно сравниваем объекты типа Date
    if (a instanceof Date && b instanceof Date) {
        return a.getTime() === b.getTime();
    }
    // Проверка отдельно элеменов в массивах
    if (Array.isArray(a) && Array.isArray(b)) {
        return a.length === b.length && a.every((value, index) => isEqual(value, b[index]));
    }
    // Убедимся, что переданные значения точно объекты
    if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
    let keysA = Object.keys(a),
        keysB = Object.keys(b);
    // Проверка свойств в объектах
    return keysA.every((key) => keysB.includes(key) && isEqual(a[key], b[key]));
};

Возможно вам потребуется учесть какие-то свои особенности, и вы можете дополнить этот метод.

Выводы

В заключение своей статьи я бы хотел подвести некоторые итоги процесса отказа от Lodash и профит, который мы получили:

  • в большинстве кейсов у нас получилось успешно заменить использование устаревшей библиотеки с уязвимостями на нативный код;

  • в кейсах, где это не представлялось возможным, мы написали собственные реализации методов, которые прозрачно работают и учитывают нюансы нашего проекта;

  • уменьшили число зависимостей в проекте.

Отказываться или нет от Lodash решать безусловно нужно индивидуально, учитывая особенности каждого проекта, но если вы приняли такое решение, то я надеюсь, что наш опыт окажется для вас полезным. Спасибо за то, что дочитали статью до конца.

Автор: vk15work

Источник [3]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/427251

Ссылки в тексте:

[1] Lodash: https://github.com/lodash/lodash

[2] "Вам не нужен Lodash": https://habr.com/ru/articles/823484/

[3] Источник: https://habr.com/ru/articles/934298/?utm_source=habrahabr&utm_medium=rss&utm_campaign=934298