Часть 3 Реализация компонентов

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

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

В первой части мы представили концепцию, набросали простой дизайн и продумали состав компонентов. Во второй части был представлен магазин Vuex для управления состоянием.

В этой части мы реализуем интерфейс и подключим его к магазину. Я подробно рассмотрю все детали шаг за шагом. Проект создается с использованием @ vue / cli, и код доступен на Github, релиз v0.2.

Структура файлов и папок

Во части 2 мы создали модуль конфигуратор и модуль аккаунт в магазине vuex. Первый отвечает за конфигуратор продукта, второй - за аутентификацию (вход и выход). Эти два модуля полностью разделены, поэтому имеет смысл создать два компонента-контейнера: AccountNav и PostCardConfigurator.

Эти компоненты контейнера обрабатывают все, что касается магазина. Это означает, что они единственные компоненты, которые отправляют действия. Они передают свойства своим дочерним компонентам. Дочерние компоненты являются чисто презентационными, они просто отображают свои шаблоны на основе props.

Взаимодействие пользователя с этими компонентами (т. Е. Нажатие кнопки) приводит к событию, отправляемому вверх родительскому элементу. События идут вверх и вверх по дереву компонентов, пока не дойдут до компонента-контейнера, который связывается с хранилищем для обновления состояния.

В компоненте App.vue это выглядит так:

Компонент ‹AccountNav /› представляет собой простую навигационную панель Bootstrap с формой входа в систему. ‹PostCardConfigurator /› является конфигуратором.

Учитывая, что эти два компонента действуют как корень для своих соответствующих обязанностей, я создал две папки «configurator» и «account» в папке / components. Все компоненты, связанные с конфигуратором, помещаются в первую папку, а компоненты, связанные с учетной записью, хранятся в папке учетной записи. Таким образом мы разделяем не только логику, но и представления (то есть компоненты).

Реализация панели навигации учетной записи

Компонент панели навигации выглядит так:

По сути, это панель навигации Bootstrap с некоторым настраиваемым стилем и компонентом ‹LoginForm /›. Компонент формы входа в систему либо показывает форму входа (не вошел в систему), либо сообщение «Вы вошли в систему» ​​с кнопкой выхода. Я знаю, что это надуманный пример, но добавление этой простой функции входа / выхода показывает, как использование модулей улучшает ремонтопригодность кода.

Презентационный компонент ‹LoginForm /› должен знать о состоянии входа в систему, чтобы показывать пользователю то, что нужно. Таким образом, мы должны передать статус авторизации как опору. Этот статус авторизации поступает из магазина и доступен в ‹AccountNav /› с помощью вспомогательной функции Vuex mapState. Кроме того, действия Vuex в отношении учетных записей также доступны в ‹AccountNav /› с помощью mapActions:

‹LoginForm /› добавляется в часть шаблона ‹AccountNav /› следующим образом:

Очевидно, компонент ‹LoginForm /› должен генерировать два события: «вход в систему» ​​и «выход из системы». Вот код этого компонента:

Он в основном отображает форму на основе статуса авторизации. При входе в систему отображается сообщение «Вы вошли в систему» ​​с кнопкой выхода.

Функции входа в систему и выхода из системы просты, они просто отправляют события родительскому элементу, который, в свою очередь, связывается с хранилищем и передает обновленный статус аутентификации обратно в качестве опоры.

Внедрение конфигуратора продукта

Как и компонент ‹AccountNav /›, компонент ‹PostCardConfigurator /› является контейнером и единственным компонентом, который взаимодействует с магазином. Для покупателя, заказавшего две открытки, это выглядит так:

Он в основном состоит из ‹ProductList /›, ‹PriceContainer /› и ‹ProceedToCheckoutButton /›. Опять же, информация о конфигураторе поступает из хранилища (снова с использованием mapState) и передается этим компонентам в качестве свойств. Кроме того, взаимодействие пользователя с этими компонентами (или их дочерними элементами) приводит к возникновению событий, которые отправляются вверх.

Этот процесс показан на диаграмме ниже:

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

Разобравшись с этим, давайте быстро рассмотрим компоненты PriceContainer и ProceedToCheckoutButton. Первый просто отображает цену (с двумя знаками после запятой), которая передается как опора, последний отображает только кнопку. Я не настраивал реальную реализацию добавления в корзину, поскольку она выходит за рамки этой серии блогов. Итак, в оставшейся части этой статьи я рассмотрю компонент ‹ProductList /›.

Внедрение ‹ProductList /›

Компонент ‹ProductList /› получает массив продуктов в качестве опоры и отвечает за отображение этого списка продуктов. Кроме того, он должен отобразить «кнопку добавления продукта», в результате чего в список будет добавлена ​​новая пустая открытка.

Итак, похоже, что мы создаем два новых компонента: ‹SingleProduct /›, в который мы передаем запись из массива продуктов, и ‹AddProduct /›.

Последнее очень просто: оно отображает кнопку и при нажатии отправляет событие «добавить продукт» своему родительскому элементу. ‹ProductList /› прослушивает это событие и передает его вверх своему «умному» родительскому элементу. Затем запускается правильное действие и мутация Vuex, и новый продукт с конфигурацией по умолчанию добавляется в массив продуктов в состоянии.

Первое не намного сложнее: нам нужно отобразить список продуктов и, следовательно, использовать директиву v-for:

Мы добавляем id в качестве опоры, так как он нам понадобится позже при сбросе или удалении продукта. Кроме того, мы добавляем v-model = ”product.config”. В моей первоначальной реализации я попытался установить весь объект продукта как v-модель, но это не сработало. Мой линтер предупреждал меня следующим сообщением: Директивы v-model не могут обновлять саму переменную итерации« product ». Поэтому я добавил дополнительное свойство (конфигурацию) к своему объектному слою продукта и использовал его как v-модель. Эту проблему лучше объяснить в этой теме Переполнение стека.

Теперь давайте посмотрим на реализацию компонента ‹SingleProduct /›, поскольку отсюда все становится интересно!

‹SingleProduct /›

Этот компонент отвечает за рендеринг одного продукта и прослушивание взаимодействия с ним пользователя:

Опять же, мы разбиваем компонент на дочерние компоненты, по одному для каждого входа. Кроме того, нам необходимо обрабатывать клики по кнопке «удалить» в правом верхнем углу и по ссылке «очистить конфигурацию» в правом нижнем углу. Первый должен удалить продукт из списка, тогда как последний сбрасывает конфигурацию обратно к значениям по умолчанию. Оба просто генерируют событие вверх с идентификатором продукта в качестве аргумента. Таким образом, «умный» корневой компонент сможет удалить или сбросить нужный продукт на основе этого идентификатора.

Вот полный код компонента ‹SingleProduct /›:

Дочерние компоненты, которые я только что упомянул, - это ‹CardChooseShape /›, ‹CardChoosePapersize /›, ‹CardChooseAmount /›, ‹CardChoosePaperquality /›, ‹CardChooseHeadline /› и ‹CardChooseMaintext /›. На самом деле только ‹CardChooseShape /› немного сложнее, поэтому мы рассмотрим его подробно в следующем разделе. Для остальных пяти компонентов мы просто устанавливаем v-модель в качестве соответствующего свойства в объекте product.config.

‹CardChooseShape /›

Компонент CardChooseShape более сложен, поскольку в нем задействованы дочерние компоненты. Нам это нужно, чтобы рисовать на бумаге разные формы, которые нравятся нашим пользователям. Мы могли бы облегчить себе жизнь, используя обычные радиокнопки, но это веселее!

В шаблоне родительского компонента компонент ‹CardChooseShape /› отображается следующим образом:

<CardChooseShape
   v-model="config.shape"
   @changed="updateParent"
   :id="id"
/>

Что касается своих родственных компонентов, этот компонент использует одно свойство из объекта конфигурации в качестве v-модели. Кроме того, он генерирует событие «изменено», которое излучается вверх методом updateParent, который мы обсуждали ранее. Все идет нормально.

Код компонента ‹CardChooseShape /› выглядит так:

Входящие реквизиты для этого компонента - это shape и id. Реализация v-модели предполагает входящее свойство «значение» и исходящее событие «вход». С опцией модели мы можем перезаписать их. Здесь мы используем «shape» как опору (вместо «value») и «changed» как событие.

Часть компонента, являющаяся шаблоном, показывает список компонентов ‹CardCustomRadioShape /›, которые мы используем, чтобы показать пользователю различные формы бумаги. Эту группу компонентов следует рассматривать как радиогруппу. У каждого свое значение (опора «theshape»), а отмеченное значение - это экземпляр, для которого «текущая» опора равна опоре «theshape».

Код для компонента ‹CardCustomRadioShape /› показан ниже:

Как и было обещано, под капотом мы просто используем тег ‹input type =” radio ”›. Этикетка стилизована под нужные формы. Атрибут «checked» радиоэлемента определяется равенством свойств «current» и «theshape».

Обратите внимание, что вычисляемое свойство inputId необходимо для получения уникальных идентификаторов для каждого экземпляра ‹CardCustomRadioShape /›. Здесь недостаточно просто использовать свойство «theshape», если на странице есть несколько экземпляров ‹SingleProduct /›. Следовательно, «id» используется для создания уникального идентификатора.

Выбор формы означает, что выполняется выбранный метод, который передает выбранную форму родителю. Родитель прослушивает это событие и передает его на один уровень выше компонента ‹SingleProduct /›. Таким образом, config.shape обновляется через привязку v-модели.

Резюме и заключение

Использование v-модели в компоненте ‹SingleProduct /› и его дочерних элементах представляет некоторые сложности, но применение v-модели в пользовательских компонентах, как мы это сделали для таких компонентов, как ‹CardChooseShape /›, дает отлично работающий конфигуратор продукта.

Оглядываясь назад на реализацию конфигуратора продукта, я должен признать, что сначала думать, а потом действовать, действительно хорошо. Начиная с наброска в части 1, определение компонентов и дочерних компонентов казалось действительно естественным.

Кроме того, размышления о состоянии, действиях и мутациях и их реализация перед реализацией компонентов были немного похожи на TDD - практику, о которой я читал раньше, но никогда не пробовал.

Следующим очевидным шагом для этого небольшого проекта будет реализация его в реальном интернет-магазине. Это может быть только Vue SPA с некоторым API / бэкэндом, но также можно добавить конфигуратор продукта в типичный проект Laravel.

Первоначально опубликовано на https://www.blog.plint-sites.nl 29 мая 2019 г.