Эксперимент. Корзина для 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 (лучше в хром). Найдите этот компонент в панели реакта

Измените какое-нибудь значение и увидите результат

Резюме: все это довольно сложно в таком виде. Но если интересно и хотите развития эксперимента, говорите. Может кто-то видит это под другим углом. А если уж совсем интересно и хотите более оформленный компонент получить, заходите в профиль и пополняйте баланс на произвольную сумму. Будет донейтом, который не пропадет просто так.