Николай Ланец
22 авг. 2017 г., 5:46

Эксперимент. Корзина для minishop2 на реакте.

Всем привет!

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

Для теста сайт любезно предоставил Михаил Воеводский, за что ему большое спасибо. Вот оригинальный сайт: http://old.demo1.minishop.dev.modx.site/ Покликайте его, посмотрите как работает корзина, ведь все познается в сравнении. Обратите внимание, что на странице просмотра заказа выводится сразу два блока корзины, и как раз здесь всплывает проблема, которую хотелось бы решить - когда меняешь кол-во товаров в одном блоке, в другом они не обновляются.

Собственно, реакт как раз такие моменты и решает с легкостью. Но я-то сегодня не ищу легких путей, я хотел, чтобы это дружило с Жучкой, чтобы не приходилось осваивать вебпак и т.п., не погружаться глубоко в javascript и прочие дебри, а просто можно было подставить свой шаблончик для рендеринга корзины. Собственно, частично я эту задачу решил. Можете посмотреть на обновленном сайте: http://demo1.minishop.dev.modx.site/

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

Покликали? Давайте тогда разберем зачем все это затевалось и что это дает.

Самое главное что мы здесь получается - совершенно иной механизм работы веб-интерфейса. В классическом варианте что происходит когда вы, к примеру, хотите изменить количество товаров в определенной позиции заказа? Вы кликаете элемент +/- или вручную меняете число в текстовом поле, после чего значение меняется, данные отправляются на сервер и от него вы получается ответ чаще всего однозначный и довольно простой, типа "Корзина успешно пересчитана" или "Корзина не была пересчитана". Вот когда все успешно, тогда еще боле менее ОК. А если нет? Надо восстановить значение текстового поля. А если их несколько (как это на оригинальном представленном сайте)? А если товарная позиция сложносоставная, с зависимыми позициями, начислениями скидок и т.п.? Тут и без ошибок-то не легко все элементы на странице учесть и актуализировать, а в случае ошибки вообще тьма.

А что мы получаем с реактом? С реактом у нас другая ситуация: у нас есть единое хранилище состояния заказа и все элементы на странице отрисовываются от этого состояния. В таком случае не надо следить за тем кто что кликнул и где что изменилось. Главное - следить за состоянием объектом состояния, а все элементы зависимые от него сами перерисуются с нужными значениями. При таком подходе интерфейсы сильно завязаны на основном модуле и становятся как заколдованные. Вот посмотрите этот ролик. Как бы я не менял значения полей, они сразу восстанавливаются, потому что заказ при этом не был изменен. Но в конце вы можете заметить, что одна позиция изменила количество и сумму, хотя видимых действий не было. Это я через react-dev-tools напрямую изменил значение одной позиции.

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

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

Как происходит подключение? Уточню, что это сейчас далеко не всем сгодится, пока так, поиграться.

1. Делаем бекап сайта. Да и вообще играемся с этим на тестовом сайте.
2. Качаете и устанавливаете из нашего репозитория https://modxclub.ru/downloads/ компонент frontbasket.
3. Качаете оттуда же modxsite (из него понадобится object-процессор). Напомню, что этот компонент больше ничего не устанавливает кроме себя, предыдущие эксперименты с ним можно забыть.
4. В системных настройках находим и удаляем значение в настройке ms2_frontend_js (так как будет наш самостоятельный обработчик).
5. Создаем себе где-нибудь на сайте 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 (лучше в хром). Найдите этот компонент в панели реакта
Измените какое-нибудь значение и увидите результат

Резюме: все это довольно сложно в таком виде. Но если интересно и хотите развития эксперимента, говорите. Может кто-то видит это под другим углом. А если уж совсем интересно и хотите более оформленный компонент получить, заходите в профиль и пополняйте баланс на произвольную сумму. Будет донейтом, который не пропадет просто так.
На самом деле, на первый взгляд реакт действительно кажется слишком перенавороченным (зачем рисовать html через js?!), но когда начинаешь решать задачи чуть сложнее отправки формы обратной связи, понимаешь, что это сильно упрощает жизнь. И даже и с такой формой программирование валидации упрощается в разы.
Сложно конечно решать задачи такого рода без вебпака....но я я все же попробую предложить свои мысли по этому поводу снова: а стоит ли отказываться от него вообще? от вебпака? Что мешает нам делать все в "правильном" виде? мне видится все же один базовый компонент реакта(ядро) и много асинхронных модулей к нему, которые подгружаться будут динамично - лучший вариант....базовый компонент(ядро) работает с бэкендом(modx), загружает к примеру список модулей, его зависимостей, чанки(шаблоны), а также обеспечивает взаимодействие модулей с бэкендом(процессором modx)....получается все наши маленькие модули, будь то для вывода корзины или постов, будут "тупыми" и будут взаимодействовать только с ядром(основным модулем реакта)....я уверен что такой подход изначально избавит от множества головняка в будущем. И возможностей при таком подходе может быть больше: к примеру модуль для вывода целиком страницы, модуль для авторизации, модуль для модальных окон....к тому же позволил бы избавиться от множества jquery кода
Тут ставка делалась на простоту установки в MODX. А так - я тоже согласен, с webpack гораздо гораздее получается
Роман, без server-side rendering это все будет совсем не то, многим это по SEO ударит. А если на стороне сервера еще средствами MODX прорисовывать, то будет задвоение шаблонизации и двойная нагрузка. Это все только усложнит проект. В общем пока нет ни в чем уверенности. Время покажет.

Добавить комментарий