Pull to refresh

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

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

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

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

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

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

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

Мое решение этой проблемы использовать — 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>

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

Хотелось бы услышать ваше мнение по поводу такого подхода, возможно есть более удачное решение.
Tags:
Hubs:
+5
Comments 0
Comments Leave a comment

Articles