Search
Write a publication
Pull to refresh

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

Level of difficultyEasy
Reading time4 min
Views1.3K

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

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

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

  • отсутствие обновлений библиотеки с 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 решать безусловно нужно индивидуально, учитывая особенности каждого проекта, но если вы приняли такое решение, то я надеюсь, что наш опыт окажется для вас полезным. Спасибо за то, что дочитали статью до конца.

Tags:
Hubs:
+1
Comments4

Articles