Использование slots во Vue на примере сниппета товара

от автора

При работе с проектами где не используется SSR (Server Side Rendering) или внедрение его невозможно, возникает проблема, что некоторые функции или логика пишутся два раза для статических элементов которые распечатывает backend и для компонентов которые рендерит Vue.

К примеру нам нужно реализовать компонент сниппета товара у которого есть ряд требований:

  • Его можно распечатать статично с бекенда со всей нужной информацией для SEO и логики
  • Его можно использовать как обычный компонент Vue, передавая параметры через v-bind, навешивая события click и т.д.
  • Он должен отображать актуальное состояние кнопки купить
  • После нажатия на кнопку «Купить», должен появится прелоудер ожидающий статус корзины

Одно из решений:

  1. Написать логику для статичных сниппетов, навешивая события click, добавлять и удалять классы load на кнопке «Купить»
  2. Отдельно написать компонент на Vue реализующий туже логику только в формате шаблонов
  3. Использовать первый пункт для вывода с бекенда, второй к примеру для вывода чистых данных полученных из состояния или ajax

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

Мое решение этой проблемы использовать — slots, а именно возможность установить контент по умолчанию, который как раз и будет содержать динамические параметры, но в тоже время если компонент будет использоваться как inline-template, то все эти параметры будут заменены на отрисованные бекендом.

Напишем компонент который сможет обработать и статику и динамику:

Листинг компонента

<template>     <div>         <!-- Если слот image не будет передан, то будет использоваться значение по умолчанию -->         <slot name="image">             <!-- Значение по умолчанию -->             <a :href="url" class="snippet__image">                 <img :src="image">             </a>         </slot>         <slot name="title">             <a :href="url" class="snippet__title">{{ title }}</a>         </slot>         <div v-if="!inCart"              @click="add"              :class="{ 'snippet__buy--load': load }"              class="snippet__control"         >             <slot name="button">                 <div class="snippet__button">Купить</div>             </slot>         </div>         <div v-if="inCart" class="snippet__control">             <div class="snippet__button">Добавлен</div>         </div>         <div v-if="load" class="snippet__load"></div>     </div> </template>  <script>     // Подключим vuex для получения состояния корзины     import { mapState, mapActions } from 'vuex'      export default {         props: {             id: {                 type: Number,                 required: true             },             url: {                 type: String             },             image: {                 type: String             },             title: {                 type: String             }         },         data() {             return {                 // Статус загрузки                 load: false,                 // Статус добавлен в корзину                 inCart: false,             }         },         computed: {             ...mapState({                 // Получаем товары из корзины                 cartItems: ({cart}) => cart.items             }),         },         mounted() {             this.$nextTick(() => {                 // Передаем статус нахождения в корзине                 this.inCart = this.cartItems.some(item => item.id === this.id)             })         },         methods: {             ...mapActions([                 // Метод добавления в корзину                 'addToCart'             ]),             add() {                 // Включаем статус загрузки                 this.load = true                 // Делаем запрос                 this.addToCart({                     id: this.id                 })             }         },         watch: {             // Смотрим за изменениями в корзине             cartItems(items) {                 // Выключаем статус загрузки                 this.load = false                 // Передаем статус нахождения в корзине                 this.inCart = items.some(item => item.id === this.id)             }         }     } </script> 

Использование компонента через backend:

<snippet :id="1" class="snippet">     <a slot="image" href="#" class="snippet__image">         <img src="photo.jpg">     </a>     <a slot="title" href="#" class="snippet__title">Товар 1</a>     <div slot="button" class="snippet__button">Купить</div> </snippet> 

Использование компонента в других компонентах:

<catalog-list>     <snippet v-for="item in items" :key="item.id" v-bind="item"></snippet> </catalog-list> 

Теперь у нас есть один компонент который можно использовать в разных случаях.

Хотелось бы услышать ваше мнение по поводу такого подхода, возможно есть более удачное решение.


ссылка на оригинал статьи https://habr.com/post/422465/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *