Упрощение html-кода в Alpine.js
01-12-2020Reading time ~ 5 min.Alpine.js 4151
Использование 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-классов.