Alpine.js — небольшой JavaScript-фреймворк, который добавляет реактивность прямо в HTML-разметку. Никакой сборки и npm-зависимостей: вы подключаете один скрипт, описываете состояние в атрибуте x-data и получаете поведение без отдельного модуля. Это удобно для серверных приложений на Django, Laravel, Rails или PHP, где страница уже отрендерена, а оживить нужно меню, формы и фильтры.

Что такое Alpine и чем он не является

Alpine повторяет идеи Vue, но в миниатюре. Реактивность строится на прокси-объектах: когда меняется свойство в x-data, Alpine пересчитывает зависящие директивы и обновляет DOM. Вес библиотеки — около 15 КБ в сжатом виде.

Alpine не даёт роутера, серверного рендеринга и экосистемы компонентов. Его ниша — немного интерактивности на серверной странице. Если экранов десятки и у них общая модель, лучше взять Vue, React или Svelte.

Подключение

Достаточно одной строки в конце body. Атрибут defer обязателен: Alpine должен запуститься после разбора DOM.

<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>

Для продакшена зафиксируйте точную версию и положите файл к себе. Плагины подключаются отдельными скриптами строго до основного файла Alpine.

Первый компонент

Состояние объявляется в x-data как обычный объект. Директивы x-show, x-bind и x-on связывают его с разметкой.

<div x-data="{ open: false }">
  <button x-on:click="open = !open">Меню</button>
  <nav x-show="open" x-transition x-cloak>
    <a href="/profile">Профиль</a>
    <a href="/logout">Выход</a>
  </nav>
</div>

Чтобы до инициализации не мелькал скрытый блок, добавьте в CSS правило [x-cloak] { display: none !important; }. После старта Alpine удалит атрибут автоматически.

Основные директивы

  • x-data — состояние компонента и его границы.
  • x-init — код при инициализации.
  • x-show и x-if — скрытие и условный рендеринг; x-if работает только на template.
  • x-bind (:class, :disabled) — привязка атрибутов.
  • x-on (@click, @submit.prevent) — обработчики и модификаторы.
  • x-model — двусторонняя привязка для форм.
  • x-for — цикл по массиву через template.

Синтаксис с двоеточием и собакой — сокращение для x-bind и x-on. Модификаторы .prevent, .stop, .outside и .window снимают необходимость писать отдельные слушатели.

Списки и формы

Цикл требует template и ключа, а привязка формы поддерживает приведение типов и задержки.

<div x-data="{ items: [{id:1, name:'Диск'}, {id:2, name:'Кабель'}], q: '' }">
  <input x-model.debounce.300ms="q" placeholder="Поиск">
  <ul>
    <template x-for="item in items.filter(i => i.name.includes(q))" :key="item.id">
      <li x-text="item.name"></li>
    </template>
  </ul>
  <label><input type="checkbox" x-model.boolean="active"> Активно</label>
</div>

Модификатор .debounce.300ms уменьшает число пересчётов при вводе, .number приводит строку к числу, .lazy обновляет модель по change, а не input. Ключ :key обязателен, если элементы добавляются, удаляются или сортируются: без него DOM переиспользуется неправильно.

Магические свойства

Внутри выражений доступны свойства с долларом: $el — текущий элемент, $refs — ссылки, $store — хранилище, $watch — подписка, $dispatch — событие, $nextTick — выполнение после обновления DOM.

<div x-data="{ count: 0 }" x-init="$watch('count', v => console.log(v))">
  <button x-on:click="count++; $dispatch('counter-changed', { count })">+1</button>
  <span x-text="count"></span>
</div>

Событие всплывает по DOM, поэтому родитель ловит его через @counter-changed.window. Это простой способ связать независимые блоки без общего хранилища.

Переиспользуемые компоненты

Если разметка повторяется, вынесите логику в Alpine.data и подключайте её по имени.

document.addEventListener('alpine:init', () => {
  Alpine.data('counter', () => ({
    count: 0,
    increment() { this.count++ },
    reset() { this.count = 0 }
  }))
})

В разметке достаточно написать x-data=»counter». Событие alpine:init гарантирует, что Alpine уже загружен, но ещё не начал обход DOM. Тот же приём используют для плагинов.

Глобальное состояние

Корзину и настройки удобно держать в Alpine.store. Хранилище доступно всем компонентам через $store, а геттеры пересчитываются автоматически.

document.addEventListener('alpine:init', () => {
  Alpine.store('cart', {
    items: [],
    get total() { return this.items.reduce((s, i) => s + i.price, 0) },
    add(item) { this.items.push(item) }
  })
})

Из разметки обращение выглядит как $store.cart.total и $store.cart.add(product). Не злоупотребляйте хранилищем: состояние одного блока держите в x-data, иначе компоненты станут связанными.

Запросы к серверу

Асинхронный метод прямо в объекте состояния — обычная практика. Не забудьте сбросить флаг загрузки в finally и показать ошибку.

<div x-data="{
  items: [], loading: false, error: null,
  async load() {
    this.loading = true; this.error = null;
    try {
      const res = await fetch('/api/items');
      if (!res.ok) throw new Error('HTTP ' + res.status);
      this.items = await res.json();
    } catch (e) { this.error = e.message; }
    finally { this.loading = false; }
  }
}" x-init="load()">
  <p x-show="loading">Загрузка…</p>
  <p x-show="error" x-text="error"></p>
</div>

После отправки формы вызовите load() повторно или добавьте элемент в массив локально. Если разметку присылает сервер, плагин morph заменит HTML, сохранив состояние компонентов.

Плагины

Официальные плагины расширяют базу: persist хранит состояние в localStorage, intersect реагирует на появление в viewport, collapse делает аккордеоны, mask форматирует ввод, sort реализует drag-and-drop, morph обновляет DOM с сохранением состояния. Каждый подключается до Alpine.

CSP и производительность

По умолчанию Alpine вычисляет выражения через new Function, что несовместимо со строгой Content-Security-Policy без unsafe-eval. Для таких проектов есть сборка @alpinejs/csp: она не выполняет строки и требует, чтобы логика была описана в Alpine.data.

Второй нюанс — объём DOM. Alpine обходит дерево при старте, поэтому тысячи элементов с директивами замедляют инициализацию. Помогают x-ignore на тяжёлых участках и разбиение страницы на небольшие компоненты. Если вы используете Turbo или htmx, вызывайте Alpine.initTree после вставки фрагмента либо настройте deferLoadingAlpine.

Диагностика

  • Alpine.version в консоли — проверка, что библиотека загрузилась.
  • Alpine.$data(document.querySelector(‘[x-data]’)) — состояние компонента.
  • Alpine.store(‘cart’) — содержимое глобального хранилища.
  • Ошибки выражений видны в консоли; частая причина — опечатка в имени свойства.
  • Если директивы не работают, проверьте порядок скриптов и defer.
  • Мерцание скрытых блоков лечится правилом [x-cloak].

Когда Alpine не нужен

Для SPA с роутингом, сложными формами и общим состоянием Alpine быстро упирается в пределы. Зато для лендинга, админки на серверном шаблоне или виджета на существующей странице он остаётся одним из самых дешёвых решений.

Итог

Alpine.js даёт реактивность там, где раньше писали десятки строк на jQuery: подключите скрипт, объявите x-data, свяжите элементы директивами. Начните с меню и формы, затем вынесите логику в Alpine.data и добавьте store для общих данных. Следите за CSP, не забывайте про x-cloak и не стройте на Alpine большое приложение.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте