При работе с проектами где не используется SSR (Server Side Rendering) или внедрение его невозможно, возникает проблема, что некоторые функции или логика пишутся два раза для статических элементов которые распечатывает backend и для компонентов которые рендерит Vue.
К примеру нам нужно реализовать компонент сниппета товара у которого есть ряд требований:
Одно из решений:
Таким образом мы напишем две логики, одна будет работать непосредственно с DOM, другая с чистыми данными.
Мое решение этой проблемы использовать — slots, а именно возможность установить контент по умолчанию, который как раз и будет содержать динамические параметры, но в тоже время если компонент будет использоваться как inline-template, все эти параметры будут заменены на отрисованные бекендом.
Напишем компонент который сможет обработать и статику и динамику:
Использование компонента через backend:
Использование компонента в других компонентах:
Теперь у нас есть один компонент который можно использовать в разных случаях.
Хотелось бы услышать ваше мнение по поводу такого подхода, возможно есть более удачное решение.
К примеру нам нужно реализовать компонент сниппета товара у которого есть ряд требований:
- Его можно распечатать статично с бекенда со всей нужной информацией для SEO и логики
- Его можно использовать как обычный компонент Vue, передавая параметры через v-bind, навешивая события click и т.д.
- Он должен отображать актуальное состояние кнопки купить
- После нажатия на кнопку «Купить», должен появится прелоудер ожидающий статус корзины
Одно из решений:
- Написать логику для статичных сниппетов, навешивая события click, добавлять и удалять классы load на кнопке «Купить»
- Отдельно написать компонент на Vue реализующий туже логику только в формате шаблонов
- Использовать первый пункт для вывода с бекенда, второй к примеру для вывода чистых данных полученных из состояния или 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>
Теперь у нас есть один компонент который можно использовать в разных случаях.
Хотелось бы услышать ваше мнение по поводу такого подхода, возможно есть более удачное решение.