Эксперимент. Корзина для minishop2 на реакте.
Всем привет!
Сегодня я затеял небольшой эксперимент с неоднозначным результатом. В общем, я решил сделать фронтовый модуль корзины для минишопа, чтобы в фронте все аджаксово и реатово было, но не просто так, а чтобы какая-то внешняя система плагинов была, то есть чтобы можно было вклиниться в логику работу модуля и отрисовывать элементы со своими собственными шаблонами. Чессказать, не думал, что без вебпака (сборщика) все так сложно и неудобно будет. Короче, в итоге потратил 13 часов вместо предполагаемых трех-четырех. Сложно передать словами все, с чем я столкнулся и сколько всего неудобств вытерпел, но не буду особо вдаваться в это, а просто покажу что было и что стало.
Для теста сайт любезно предоставил Михаил Воеводский, за что ему большое спасибо. Вот оригинальный сайт: http://old.demo1.minishop.dev.modx.site/ Покликайте его, посмотрите как работает корзина, ведь все познается в сравнении. Обратите внимание, что на странице просмотра заказа выводится сразу два блока корзины, и как раз здесь всплывает проблема, которую хотелось бы решить - когда меняешь кол-во товаров в одном блоке, в другом они не обновляются.

Собственно, реакт как раз такие моменты и решает с легкостью. Но я-то сегодня не ищу легких путей, я хотел, чтобы это дружило с Жучкой, чтобы не приходилось осваивать вебпак и т.п., не погружаться глубоко в javascript и прочие дебри, а просто можно было подставить свой шаблончик для рендеринга корзины. Собственно, частично я эту задачу решил. Можете посмотреть на обновленном сайте: http://demo1.minishop.dev.modx.site/
Я убрал пока блок непосредственного оформления заказа, для эксперимента это было бы уже слишком. Так же предупреждаю, что я не во всех браузерах тестировал, но должно работать в хроме и огнелисе.
Покликали? Давайте тогда разберем зачем все это затевалось и что это дает.
Самое главное что мы здесь получается - совершенно иной механизм работы веб-интерфейса. В классическом варианте что происходит когда вы, к примеру, хотите изменить количество товаров в определенной позиции заказа? Вы кликаете элемент +/- или вручную меняете число в текстовом поле, после чего значение меняется, данные отправляются на сервер и от него вы получается ответ чаще всего однозначный и довольно простой, типа "Корзина успешно пересчитана" или "Корзина не была пересчитана". Вот когда все успешно, тогда еще боле менее ОК. А если нет? Надо восстановить значение текстового поля. А если их несколько (как это на оригинальном представленном сайте)? А если товарная позиция сложносоставная, с зависимыми позициями, начислениями скидок и т.п.? Тут и без ошибок-то не легко все элементы на странице учесть и актуализировать, а в случае ошибки вообще тьма.
А что мы получаем с реактом? С реактом у нас другая ситуация: у нас есть единое хранилище состояния заказа и все элементы на странице отрисовываются от этого состояния. В таком случае не надо следить за тем кто что кликнул и где что изменилось. Главное - следить за состоянием объектом состояния, а все элементы зависимые от него сами перерисуются с нужными значениями. При таком подходе интерфейсы сильно завязаны на основном модуле и становятся как заколдованные. Вот посмотрите этот ролик. Как бы я не менял значения полей, они сразу восстанавливаются, потому что заказ при этом не был изменен. Но в конце вы можете заметить, что одна позиция изменила количество и сумму, хотя видимых действий не было. Это я через react-dev-tools напрямую изменил значение одной позиции.

Теперь все работает по-другому. Вы через любой элемент воздействуете на основной модуль, тот отправляет запрос на сервер, получает ответ, накатывает на себя полученные данные и все элементы отрисовываются исходя из актуальных данных. При таком подходе даже если пользователь открыл несколько вкладок и начнет поочередно добавлять-удалять товары в корзине, каждый раз корзина будет актуализироваться при последнем запросе.
Ладно, это все теория. Может кто-то хочет сам покликать это и вникнуть поглубже? Сразу скажу, там все довольно сложно, но может кому-то будет интересно. Во всяком случае я попытался все упростить.
Как происходит подключение? Уточню, что это сейчас далеко не всем сгодится, пока так, поиграться.
- Делаем бекап сайта. Да и вообще играемся с этим на тестовом сайте.
- Качаете и устанавливаете из нашего репозитория https://modxclub.ru/downloads/ компонент frontbasket.
- Качаете оттуда же modxsite (из него понадобится object-процессор). Напомню, что этот компонент больше ничего не устанавливает кроме себя, предыдущие эксперименты с ним можно забыть.
- В системных настройках находим и удаляем значение в настройке ms2_frontend_js (так как будет наш самостоятельный обработчик).
- Создаем себе где-нибудь на сайте js-файл вот с таким содержимым: https://gist.github.com/Fi1osof/00fae120abc921f94a1dc566ad85768c не забываем подключить его в шаблонах сайта. В этот момент при загрузке страницы скрипт уже должен инициироваться на странице. Остается только в нужных частях страниц прописать теги, чтобы скрипт знал куда на странице вклиниваться. 5.1 Миникорзине прописываем id="msMiniCart", именно в этот элемент будет наша миникорзина рендериться (заменит имеющийся элемент). 5.2 На странице просмотра заказа блок товаров должен быть обернут в атрибут data-type="order-details". 5.3 В карточках товаров структура формы и сабмита должна быть такая: 'form.ms2_form [type=submit]' или в представленном файле поправьте эту строчку.
Зачем вообще это все нужно?! Ведь так все сложно!
Да, все это действительно не легко. Но это все потому что не в вебпаке и шаблонизация в пользовательском файле много места занимает. А так все самое интересное из этого файла укладывается буквально в эти строки:
'use strict';
let FrontBasketTemplates = class {
constructor(basket){
this.basket = basket;
}
componentDidMount(){
// Выполняется, когда модуль отрисовался в HTML страницы
}
renderHtmlElement(node, props){
return this.basket.renderHtmlElement(node, props);
}
miniBasket(){
let miniBasketBlock = this.getMiniBasketBlock();
return [miniBasketBlock];
}
getMiniBasketBlock(){
let {
basket,
} = this;
let {
total_count,
total_cost,
} = basket.state;
let element;
let total_count_str = total_count.toLocaleString();
let total_cost_str = total_cost.toLocaleString();
let basketNode = $(`<div></div>`);
if(total_count > 0){
let notEmptyCart = $(`<div class="not_empty" style="display:block;">
<a href="/cart.html" class="nav-link">
<i class="ec ec-shopping-bag"></i>
<span class="cart-items-count count ms2_total_count" data-type="total-count">
${total_count_str}
</span>
<span class="cart-items-total-price total-price"><span class="amount ms2_total_cost">
</span> ${total_cost_str} руб.</span>
</a>
</div>`);
basketNode.html(notEmptyCart);
}
else{
let emptyCart = $(`<div class="empty">
<a href="/cart.html" class="nav-link">
<i class="ec ec-shopping-bag"></i>
<span class="cart-items-total-price total-price"><span class="amount">0</span> руб.</span>
</a>
</div>`);
emptyCart.show();
basketNode.html(emptyCart);
}
return this.renderHtmlElement(basketNode, {
key: "miniBasketBlock",
onMount: (onMount) => {
}
})
}
componentDidUpdate(prevProps, prevState){
// Здесь можно перехватить изменения в состоянии корзины
}
}
window.FrontBasketTemplates = FrontBasketTemplates;
Что здесь происходит? Реактовый компонент смотрит есть ли в объекте window класс FrontBasketTemplates и если есть, инициирует его. Сейчас главная точка входа - метод miniBasket.
Там, как вы видите, по цепочке возвращается или карточка пустой корзины, или корзины с товарами. Повторюсь, перерисовка происходит при каждом изменении корзины (если корзина менялась по религии). Изменить корзину можно из любого из этих методов через вызов this.basket.setState();
Кто хочет с этим побольше поиграться, почти ничего не делая, советую в браузер установить react-dev-tools (лучше в хром). Найдите этот компонент в панели реакта
Измените какое-нибудь значение и увидите результат
Резюме: все это довольно сложно в таком виде. Но если интересно и хотите развития эксперимента, говорите. Может кто-то видит это под другим углом. А если уж совсем интересно и хотите более оформленный компонент получить, заходите в профиль и пополняйте баланс на произвольную сумму. Будет донейтом, который не пропадет просто так.