Перейти к содержанию

Alpine⚓︎

AlpineJS — это лёгкая JavaScript-библиотека, которая позволяет очень просто добавлять интерактивность на стороне клиента в ваши веб-страницы. Изначально она создавалась как дополнение к инструментам вроде Livewire, когда нужна более «чистая» JavaScript-утилита для точечного добавления интерактивности в приложение.

Livewire поставляется с уже встроенным Alpine, поэтому устанавливать его отдельно в проект не нужно.

Самое лучшее место, чтобы изучить использование AlpineJS — это официальная документация Alpine.

Простейший компонент на Alpine⚓︎

Чтобы заложить основу для дальнейшего материала, вот один из самых простых и показательных примеров компонента на Alpine. Небольшой «счётчик», который показывает число на странице и позволяет увеличивать его нажатием на кнопку:

<!-- Объявляем объект с данными JavaScript... -->
<div x-data="{ count: 0 }">
    <!-- Выводим текущее значение count внутри элемента... -->
    <h2 x-text="count"></h2>

    <!-- При клике увеличиваем значение count на 1... -->
    <button x-on:click="count++">+</button>
</div>

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

Использование Alpine внутри Livewire⚓︎

Давайте рассмотрим более реалистичный пример использования компонента Alpine внутри компонента Livewire.

Ниже приведён простой компонент Livewire, который отображает детали модели поста из базы данных. По умолчанию показывается только заголовок поста:

<div>
    <h1>{{ $post->title }}</h1>

    <div x-data="{ expanded: false }">
        <button type="button" x-on:click="expanded = ! expanded">
            <span x-show="! expanded">Показать содержимое поста...</span>
            <span x-show="expanded">Скрыть содержимое поста...</span>
        </button>

        <div x-show="expanded">
            {{ $post->content }}
        </div>
    </div>
</div>

Используя Alpine, мы можем скрывать содержимое поста, пока пользователь не нажмёт кнопку «Показать содержимое поста...». В этот момент свойство expanded в Alpine будет установлено в true, и содержимое будет показано на странице, потому что x-show="expanded" используется для передачи Alpine контроля над видимостью содержимого поста.

Это пример того, где Alpine особенно хорош: добавление интерактивности в ваше приложение без лишних обращений на сервер Livewire.

Управление Livewire из Alpine с помощью $wire⚓︎

Одна из самых мощных возможностей, доступных вам как разработчику Livewire, — это $wire. Объект $wire — это магический объект, доступный во всех ваших Alpine-компонентах, которые используются внутри Livewire.

Можно думать о $wire как о шлюзе из JavaScript в PHP. Он позволяет вам получать доступ и изменять свойства компонента Livewire, вызывать методы компонента Livewire и делать гораздо больше — всё это изнутри AlpineJS.

Доступ к свойствам Livewire⚓︎

Вот пример простой утилиты «подсчёт символов» в форме создания поста. Она будет мгновенно показывать пользователю, сколько символов содержится в содержимом его поста, пока он печатает:

<form wire:submit="save">
    <!-- ... -->

    <input wire:model="content" type="text">

    <small>
        Количество символов: <span x-text="$wire.content.length"></span>
    </small>

    <button type="submit">Сохранить</button>
</form>

Как вы можете видеть, в приведённом выше примере x-text используется для того, чтобы позволить Alpine управлять текстовым содержимым элемента <span>. x-text принимает любое JavaScript-выражение внутри себя и автоматически реагирует на обновление любых зависимостей. Поскольку мы используем $wire.content для доступа к значению свойства $content, Alpine будет автоматически обновлять текстовое содержимое каждый раз, когда $wire.content обновляется со стороны Livewire — в данном случае благодаря wire:model="content".

Изменение свойств Livewire⚓︎

Вот пример использования $wire внутри Alpine для очистки поля «title» в форме создания поста.

<form wire:submit="save">
    <input wire:model="title" type="text">

    <button type="button" x-on:click="$wire.title = ''">Очистить</button>

    <!-- ... -->

    <button type="submit">Сохранить</button>
</form>

Пока пользователь заполняет приведённую выше форму Livewire, он может нажать «Очистить», и поле title будет очищено без отправки сетевого запроса от Livewire. Взаимодействие будет «мгновенным».

Вот краткое объяснение того, что происходит, чтобы это сработало:

  • x-on:click указывает Alpine слушать событие клика на элементе кнопки
  • При клике Alpine выполняет указанное JS-выражение: $wire.title = ''
  • Поскольку $wire — это магический объект, представляющий компонент Livewire, все свойства вашего компонента можно читать или изменять прямо из JavaScript
  • $wire.title = '' устанавливает значение свойства $title в вашем компоненте Livewire в пустую строку
  • Любые утилиты Livewire, такие как wire:model, мгновенно отреагируют на это изменение — всё без лишних обращений к серверу
  • При следующем сетевом запросе Livewire свойство $title будет обновлено до пустой строки уже на бэкенде

Вызов методов Livewire⚓︎

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

Вот пример использования Alpine для прослушивания события blur на поле ввода и запуска сохранения формы. Событие blur отправляется браузером, когда пользователь нажимает Tab, чтобы убрать фокус с текущего элемента и перевести его на следующий элемент на странице:

<form wire:submit="save">
    <input wire:model="title" type="text" x-on:blur="$wire.save()">

    <!-- ... -->

    <button type="submit">Сохранить</button>
</form>

Обычно в такой ситуации вы бы просто использовали wire:model.live.blur="title", однако для демонстрационных целей полезно показать, как это можно реализовать с помощью Alpine.

Передача параметров⚓︎

Вы также можете передавать параметры в методы Livewire, просто передавая их в вызов метода через $wire.

Рассмотрим компонент с методом deletePost() примерно такого вида:

<?php

public function deletePost($postId)
{
    $post = Post::find($postId);

    // Проверяем, имеет ли пользователь право удалять...
    auth()->user()->can('update', $post);

    $post->delete();
}

Теперь вы можете передать параметр $postId в метод deletePost() из Alpine вот так:

<button type="button" x-on:click="$wire.deletePost(1)">

В общем случае что-то вроде $postId обычно генерируется в Blade. Вот пример того, как с помощью Blade определить, какой именно $postId будет передан из Alpine в метод deletePost():

@foreach ($posts as $post)
    <button type="button" wire:key="{{ $post->id }}" x-on:click="$wire.deletePost({{ $post->id }})">
        Удалить "{{ $post->title }}"
    </button>
@endforeach

Если на странице три поста, приведённый выше шаблон Blade отрендерится в браузере примерно так:

<button type="button" x-on:click="$wire.deletePost(1)">
    Удалить «The power of walking»
</button>

<button type="button" x-on:click="$wire.deletePost(2)">
    Удалить «How to record a song»
</button>

<button type="button" x-on:click="$wire.deletePost(3)">
    Удалить «Teach what you learn»
</button>

Как вы можете видеть, мы использовали Blade, чтобы подставить разные ID постов в выражения Alpine x-on:click.

Подводные камни при передаче параметров из Blade⚓︎

Это чрезвычайно мощная техника, но она может запутывать при чтении ваших Blade-шаблонов. На первый взгляд бывает сложно понять, какая часть — это Blade, а какая — Alpine. Поэтому полезно проверять отрендеренный HTML на странице, чтобы убедиться, что всё подставилось именно так, как вы ожидали.

Вот пример, который часто сбивает с толку:

Допустим, вместо обычного ID ваша модель Post использует UUID в качестве первичного ключа (ID — это целые числа, а UUID — длинные строки символов).

Если мы отрендерим следующий код точно так же, как делали с ID, возникнет проблема:

<!-- Предупреждение: это пример проблемного кода... -->
<button
    type="button"
    x-on:click="$wire.deletePost({{ $post->uuid }})"
>

Приведённый выше шаблон Blade отрендерит в вашем HTML следующее:

<!-- Предупреждение: это пример проблемного кода... -->
<button
    type="button"
    x-on:click="$wire.deletePost(93c7b04c-c9a4-4524-aa7d-39196011b81a)"
>

Обратите внимание на отсутствие кавычек вокруг строки UUID? Когда Alpine попытается вычислить это выражение, JavaScript выдаст ошибку: "Uncaught SyntaxError: Invalid or unexpected token".

Чтобы исправить это, нужно добавить кавычки вокруг выражения Blade следующим образом:

<button
    type="button"
    x-on:click="$wire.deletePost('{{ $post->uuid }}')"
>

Теперь приведённый выше шаблон отрендерится корректно, и всё будет работать как ожидается:

<button
    type="button"
    x-on:click="$wire.deletePost('93c7b04c-c9a4-4524-aa7d-39196011b81a')"
>

Обновление компонента⚓︎

Вы можете легко обновить компонент Livewire (запустить сетевой запрос на сервер для повторного рендеринга Blade-представления компонента) с помощью $wire.$refresh():

<button type="button" x-on:click="$wire.$refresh()">

Совместное использование состояния через $wire.entangle⚓︎

Скорее всего, вам это не понадобится

Почти во всех случаях следует использовать $wire для прямого доступа к свойствам Livewire из Alpine вместо $wire.entangle(). Entangle создаёт дублированное состояние, что может приводить к проблемам с предсказуемостью и производительностью. Этот API поддерживается только ради обратной совместимости, но для нового кода его использование не рекомендуется.

Не используйте директиву Blade @entangle — она устарела и вызывает проблемы при удалении элементов DOM.

Для редких случаев, когда вам действительно нужна двунаправленная синхронизация состояния между Alpine и Livewire, можно использовать $wire.entangle():

<div x-data="{ open: $wire.entangle('showDropdown') }">
    <button x-on:click="open = true">Показать ещё...</button>

    <ul x-show="open">
        <li><button wire:click="archive">Архивировать</button></li>
    </ul>
</div>

По умолчанию изменения откладываются до следующего запроса Livewire. Используйте .live, чтобы синхронизировать сразу:

<div x-data="{ open: $wire.entangle('showDropdown').live }">

Использование директивы @js⚓︎

Если вам нужно вывести данные из PHP для прямого использования в Alpine, вы можете воспользоваться директивой @js.

<div x-data="{ posts: @js($posts) }">
    ...
</div>

Ручная сборка Alpine в вашем JavaScript-бандле⚓︎

По умолчанию JavaScript Livewire и Alpine автоматически подключается на каждую страницу с Livewire.

Это идеально для простых проектов, однако вы можете захотеть добавить собственные компоненты Alpine, хранилища (stores) и плагины в свой проект.

Подключить Livewire и Alpine через собственный JavaScript-бандл на странице довольно просто.

Сначала нужно добавить директиву @livewireScriptConfig в ваш файл макета (layout) следующим образом:

<html>
<head>
    <!-- ... -->
    @livewireStyles
    @vite(['resources/js/app.js'])
</head>
<body>
    {{ $slot }}

    @livewireScriptConfig
</body>
</html>

Это позволяет Livewire предоставить вашему бандлу определённую конфигурацию, необходимую для корректной работы приложения.

Теперь вы можете импортировать Livewire и Alpine в ваш файл resources/js/app.js следующим образом:

import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';

// Здесь регистрируйте любые директивы, компоненты или плагины Alpine...

Livewire.start()

Вот пример регистрации пользовательской директивы Alpine под названием x-clipboard в вашем приложении:

import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';

Alpine.directive('clipboard', (el) => {
    let text = el.textContent

    el.addEventListener('click', () => {
        navigator.clipboard.writeText(text)
    })
})

Livewire.start()

Теперь директива x-clipboard будет доступна во всех ваших Alpine-компонентах в приложении Livewire.

Смотрите также⚓︎

  • Свойства — Доступ к свойствам Livewire из Alpine через $wire
  • Действия — Вызов действий Livewire из Alpine
  • JavaScript — Выполнение пользовательского JavaScript в компонентах
  • События — Отправка и прослушивание событий с помощью Alpine