Давайте создадим модуль корзины покупок для вашего сайта / веб-приложения.

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

Модули — это крошечные инкапсулированные фрагменты кода, которые обычно выполняют небольшую понятную задачу или решают уникальную проблему. Как правило, они поддаются модульному тестированию, и практически любому программисту, включая вас позже, будет легко быстро понять, что они делают. Все это измеримые преимущества. Существует несколько шаблонов, которые можно использовать для создания модуля приложения. Мы сосредоточимся на очень типичном шаблоне объектно-ориентированного проектирования (их много, подробнее здесь), который я называю шаблоном конструктор модуля.

Имеет 3 структуры.

1. Функция-конструктор, которая инициализирует модуль.

2. Объект, который создается внутри модуля и является возвращаемым значением функции.

3. Методы и свойства объекта, являющиеся доступными блоками кода, которые можно использовать повторно.

Давайте создадим базовый на основе простой страницы оформления заказа на веб-сайте. Мы создадим модуль корзины для веб-приложения.

function Cart() {
    let Cart = {};
    return Cart;
}
export default Cart;

Это основной формат модуля. Это читается как функция с именем Cart, при вызове она генерирует объект с именем Cart, инициализирует все, что в нем установлено, и возвращает это. Функция просто конструирует объект, и в этом ее цель. Наконец, есть оператор экспорта (это синтаксис ES6, так что идите здесь, если вы не знакомы), и это делает его доступным для импорта. Вы также можете использовать IIFE для этого, если вы не хотите экспортировать его куда-либо, в любом случае это будет работать.

Примечание. Некоторые старые браузеры не поддерживают операторы импорта или экспорта, поэтому, если вам нужно их поддерживать, вам потребуется использовать транспилятор, такой как Babel.

Предположим, вы создали каталог в главном корне вашего приложения с именем modules и сохранили его в файле с именем cart.js.

Где-то еще в вашем приложении вы можете импортировать модуль и создать его экземпляр, например…

import Cart from ‘./modules/cart’;
let newCart = Cart();

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

function Cart() {
    let Cart = {
        lineItems: [],
        addItem: (item) => {},
        removeItem: (item) => {},
        updateQuantity: (index, qty) => {}
    };
    return Cart;
}

Теперь ваш экземпляр newCart имеет доступ ко всем методам, так что вы можете связать такие вещи, как newCart.addItem(item);

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

Методы:

Сначала рассмотрим addItem. Добавление элемента включает два возможных пограничных случая. Добавление нового элемента в коллекцию или обновление существующего в коллекции. Поэтому addItem должен обрабатывать оба этих параметра.

addItem : (item) {
    let inCart = Cart.lineItems.findIndex(function(cartItem) {
        return cartItem.sku === item.sku;
    });
    if (inCart != -1) {
        Cart.lineItems.push(item);
    } else {
        Cart.lineItems[index].quantity += item.qty;
    }
}

Это может показаться сложным, поэтому позвольте мне сломать это. Этот код гласит... Посмотрите, нет ли товара, который я пытаюсь добавить, в корзину. Если этого товара еще нет в корзине, добавьте его, в противном случае увеличьте количество соответствующего товара на единицу.

Теперь в любом месте вашего приложения вы можете вызвать что-то вроде…

Cart.addItem({ sku: 1234567890, qty: 1 });

Теперь давайте защитим метод нашего модуля от злоумышленников или разработчика, допустившего ошибку. Я собираюсь использовать пару примеров генерирующих или консольных ошибок, но имейте в виду, что существует множество способов обработки ошибок.

if (!item) throw `Invalid item passed to cart: ${item}`;
if (!item.hasOwnProperty(‘sku’)) console.error(‘Product missing a sku, so it cannot be added to the cart : ${item.sku}`);if (!item.hasOwnProperty(‘qty’)) item.qty = 1;

Примечание. Это обрабатывает только три случая. Если добавление было вызвано без добавления, если элемент не имеет артикула, и если количество отсутствует, по умолчанию используется значение 1. Вы захотите использовать эти типы блоков в любом из ваших методов, чтобы убедиться, что вы защитить целостность данных.

Давайте поработаем над removeItem.

removeItem: (item) {
    let index = Cart.lineItems.findIndex(function(cartItem){
        return cartItem.sku === item.sku;
    });
    Cart.lineItems.splice(index, 1);
}

Это просто гласит: найдите, где этот элемент находится в коллекции, и когда вы найдете его местоположение, удалите его.

Вы можете вставить аналогичные проверки, чтобы подтвердить свой товар, если хотите. Иногда, если я обнаруживаю, что повторно использую аналогичные проверки проверки, я пишу отдельный модуль для проверки, который может называться «Валидатор», и все, что он делает, — это принимает объекты для проверки. Для простоты и того факта, что вы, вероятно, поняли идею, мы сосредоточимся только на оставшихся методах.

Наконец, давайте выполним updateQuantity, он принимает элемент и новое количество.

updateQuantity : (item, qty) => {
    let index = Cart.lineItems.findIndex(function(cartItem) {
        return carItem.sku === item.sku
    });
    Cart.lineItems[index].qty += qty;
};

Это ищет предмет в коллекции и обновляет его количество… Подождите, разве мы уже не делали это где-то раньше? Да, мы сделали это в операторе else метода add. Кроме того, разве мы не получили индекс элемента в коллекции раньше? Ага, это тоже. Не будем повторяться. Мы можем повторно использовать этот updateMethod внутри, и мы можем добавить новый внутренний метод для получения индекса этого элемента, чтобы у нас не было повторяющегося кода. Давайте теперь поговорим о внутренней оптимизации, чтобы прояснить ситуацию.

Примечание. В JS нет закрытых методов, таких как строго типизированные компилируемые языки, поэтому, к сожалению, вы не можете скрыть внутренние методы от внешней области видимости. Кроме того, существует передовая практика, которую поймет большинство разработчиков. Начните свой внутренний метод с символа _, и это означает, что он не предназначен для вызова извне. Например, _getCartItemIndex

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

_getCartItemIndex : (item) => {
    return Cart.lineItems.findIndex(function(cartItem) {
        return item.sky === item.sku
    });
}

Теперь мы обновляем все наши методы

addItem : (item) {
    let inCart = _getCartItemIndex(item);
    if (inCart != -1) {
        Cart.lineItems.push(item);
    } else {
        Cart.lineItems[index].quantity += item.qty;
    }
},
removeItem: (item) {
    let index = _getCartItemIndex(item);
    if (index != -1) Cart.lineItems.splice(index, 1);
},
updateQuantity : (item, qty) => {
    let index = _getCartItemIndex(item);
    if (index != -1) Cart.lineItems[index].qty += qty;
};

Это намного чище, и у нас есть только этот блок запроса индекса в одном месте…

Затем обновите метод addItem, чтобы использовать updateQuantity.

addItem : (item) {
    let inCart = _getCartItemIndex(item);
    if (inCart != -1) {
        Cart.lineItems.push(item);
    } else {
        Cart.updateQuantity(item, item.qty);
    }
}

Возможно, это и прояснило ситуацию, но есть еще одно лишнее место, можете его заметить?

Это вызов, который мы только что сделали для updateQuantity(item). Видите, что местоположение элемента уже было оценено в методе addItem, и в этот момент мы вызываем updateQuantity, мы уже знаем его индекс. Поэтому было бы удобно, если бы метод updateMethod мог напрямую обновлять коллекцию без повторного поиска. Давайте создадим способ передать это значение и сделаем так, чтобы это произошло.

updateQuantity(item, qty, index) { 
    // Here index is an optional argument
    if (!index) index = _getCartItemIndex(item);
    if (index != -1) Cart.lineItems[index].qty += qty;
}

Это должно сделать это, если вы не передаете индекс, он идет и получает его, иначе он попадает в обновление количества. Теперь мы можем обновить addItem, чтобы включить это.

addItem : (item) {
    let inCart = _getCartItemIndex(item);
    if (inCart != -1) {
        Cart.lineItems.push(item);
    } else {
        Cart.updateQuantity(item, item.qty, inCart);
    }
}

Наконец, давайте добавим функцию проверки для обработки тех ошибок, которые мы делали ранее.

_validator : (item) {
    let valid = true;
    if (!item) {
        console.error('Invalid item passed to cart', ${item}); 
        valid = false; 
    }
    if (!item.hasOwnProperty(‘sku’)) {
        console.error(‘Product missing a sku, so it cannot be added to the cart : ${item.sku}`);
        valid = false;
    }
    return valid;
}

Вот весь модуль ниже. Это должно обрабатывать наши варианты использования по мере необходимости. Теперь вы можете писать модульные тесты для каждого метода и, следовательно, знать, что ваш код будет работать, еще до того, как он будет вызван. Если бы в какой-то момент в приложении внутри этого модуля возникла ошибка, консоль распечатала бы ошибку, расположенную в cart.js [имя_метода], и строку, в которой она произошла. Это намного проще отладить, чем неясные ошибки области видимости, которые вы могли бы увидеть, если бы вы только что написали статические функции во всем своем приложении.

function Cart() {
    let Cart = {
        lineItems: [],
        addItem : (item) {
            if (!Cart._validator()) return
            if (!item.hasOwnProperty(‘qty’)) item.qty = 1;
            let inCart = _getCartItemIndex(item);
            if (inCart != -1) {
                Cart.lineItems.push(item);
            } else {
                Cart.updateQuantity(item, item.qty, inCart);
            }
        },
        removeItem: (item) {
            let index = _getCartItemIndex(item);
            if (index != -1) Cart.lineItems.splice(index, 1);
        },
        updateQuantity(item, qty, index) { // index is optional
            if (!index) index = _getCartItemIndex(item);
            if (index != -1) Cart.lineItems[index].qty += qty;
        },
        _getCartItemIndex : (item) => {
            return Cart.lineItems.findIndex(function(cartItem) {
                return item.sky === item.sku
            });
        },
       _validator : (item) {
           let valid = true;
           if (!item) {
               console.error('Invalid item passed to cart', ${item}); 
               valid = false; 
           }
           if (!item.hasOwnProperty(‘sku’)) {
               console.error(‘Product missing a sku, so it cannot be   added to the cart : ${item.sku}`);
               valid = false;
           }
           return valid;
       }
    };
    return Cart;
}
export default Cart;

Это крошечный модуль, который может многое. Его можно повторно использовать практически для любой корзины, и все, что вам нужно сделать, это убедиться, что форматирование объекта совпадает, или адаптировать модуль для обработки вашего формата объекта. В целом, такие модули — гораздо лучшая практика, чем попытки изобретать велосипед каждый раз. Оставайтесь молодыми, мыслите далеко и программируйте чисто.

Чао