Упрощение html-кода в Alpine.js
01-12-2020Время чтения ~ 5 мин.Alpine.js 5128
Использование Alpine делает html-код несколько сложнее, поскольку на уровень HTML по сути выносится логика JavaScript. Да, директивы несложные, но, если стоит задача повторить элемент несколько раз, то хочется всё-таки какого-то упрощения.
Для этого в Alpine.js используется директива x-spread, которая ссылается на функцию-обработчик текущего компонента. Общая схема получается такой: в x-data прописываем функцию инициализации, которая и содержит объект со всеми функциями компонента.
Речь в статье идёт о версии Alpine 2. В новых версиях это работать так уже не будет. :-(
В таком варианте можно говорить о компонентом подходе в Alpine, где функции могут использоваться многократно. Рассмотрим пару примеров.
«Переключалка» css-класса
Этот пример я уже приводил, он простой.
<div x-data="{show: false}" @click="show = !show" :class="{'t-red': show}">toggle class click</div>
Если внимательно посмотреть, то мы увидим, что основной код — это логика компонента. Единственная вариативная часть — css-класс, который меняется. Если «спрятать» всю логику, то получится так:
<div x-data="toggleClick('t-red')" x-spread="trigger">toggle class click</div>
<div x-data="toggleClick('t-green')" x-spread="trigger" class="t-purple">toggle class click</div>
Сама функция:
<script>
function toggleClick(a) {
return {
show: false,
trigger: {
['@click']() {
this.show = !this.show;
},
[':class']() {
return this.show ? a : '';
},
},
}
}
</script>
Она возвращает обычный объект с полями для переменных (как show) и данные для x-spread, которые представляют собой Alpine-директивы с колбэк-функциями. То есть по сути мы перенести все директивы в js-функцию в исходном виде.
Разница разве что в том, что для переменных внутри нужно использовать this, а также часть логики нужно делать как обычно в JS (см. if-условие).
Функция, конечно, увеличила общий размер кода, но, с другой стороны, его использование в HTML существенно улучшилось. Мы не только спрятали логику, но и сократили общий размер html-кода.
Этот пример достаточно простой, поэтому рассмотрим что-то поинтересней.
Модальное окно
Возьмём код из предыдущей статьи и переделаем его на x-spread. Вначале сама функция.
<script>
function modalInit(classContainer = 'bg-op60') {
return {
modalShow: false,
trigger: {
['@click']() {
this.modalShow = true
},
},
container: {
['x-show.transition.in.opacity.duration.500ms.out.duration.400ms']() {
return this.modalShow
},
[':class']() {
return 'pos-fixed pos0-l pos0-t h100 w100 z-index99 ' + classContainer;
},
},
content: {
['@click.away']() {
this.modalShow = false
},
[':class']() {
return 'pos-absolute w100 w600px-max bg-white';
},
[':style']() {
return 'left: 50%; top: 50%; transform: translate(-50%, -50%);';
},
},
close: {
['@click']() {
this.modalShow = false
},
[':class']() {
return 'pos-absolute pos10-r pos0-t t150 t-white hover-t-gray200 cursor-pointer';
},
},
}
}
</script>
Вот так можно использовать:
<div x-data="modalInit()">
<button x-spread="trigger" class="button button1">Open Modal</button>
<div x-spread="container" x-cloak>
<div x-spread="content">
<span x-spread="close">×</span>
<div class="pad10 bg-blue600 t-white">Header</div>
<div class="pad20-tb pad10-rl">Content</div>
<div class="pad10 bg-gray100 t-gray600">Footer</div>
</div>
</div>
</div>
Согласитесь, использовать такой вариант намного приятней. Даже если мы взяли бы любой другой «плагин» (для jQuery или отдельную разработку), то HTML-разметка всё равно получилась бы похожей, где вместо x-spread и x-data пришлось бы использовать css-классы или id. Поскольку функция может использоваться многократно, то модальных форм можно разместить сколько угодно — они все будут работать не зависимо друг от друга.
Удивительно насколько автор Alpine.js Caleb Porzio продумал даже такие «мелочи». :-)
Когда упрощать не имеет смысла
В некоторых случаях введение функции для x-spread только ухудшит код. Например я пытался сделать обычные табы, но в итоге код Alpine оказался самым простым, несмотря на то, что там присутствует логика.
<div x-data="{tab: 1}">
<button :class="{'bg-blue': tab == 1}" @click="tab = 1" class="button">Foo</button>
<button :class="{'bg-blue': tab == 2}" @click="tab = 2" class="button">Bar</button>
<div class="pad20 bordered">
<div x-show="tab == 1" class="animation-fade">Вкладка Foo</div>
<div x-show="tab == 2" class="animation-fade" x-cloak>Вкладка Bar</div>
</div>
</div>
Алгоритм таков, что в любом случае нужно задавать номер (имя) таба как для вкладки, так и для кнопки переключения. Так же мы не знаем, сколько именно будет вкладок — это уже решает конечный пользователь.
На самом деле этот код меньше, чем если использовать «традиционный» подход. Чтобы получить доступ к элементам табов, приходится использовать уникальные ID, а также предопределённые классы и data-атрибуты. А в коде с Alpine достаточно лишь задать произвольный номер слайда. Всё остальное — это обычные имена css-классов.