Обзор LiveWire 3 и Volt
Приветствую всех поклонников Laravel!
Эта статья-обзор новой, уже третьей версии Livewire. Решил сделать эту статью после выпуска на youtube-канале видео обзора Livewire, который понравился аудитории. Для тех, кто больше любит видео, то оно тут:
Ну и заодно также взглянем на новинку — Volt. Думаю, многие из вас ждали этот обзор и особенно обзор Volt.
Документация
Первое отличие — документация 3 версии Livewire переехала на поддомен laravel.com. Это https://livewire.laravel.com/ и заодно обновили дизайн.
Установка
Перейдем в документацию https://livewire.laravel.com/docs/quickstart в раздел установки.
Установка сильно упростилась и теперь зависимости устанавливаются с помощью композера:
composer require livewire/livewire
Следующий шаг — это публикация config:
php artisan livewire:publish --config
На этом всё, друзья. Теперь в основной шаблон не нужно добавлять @livewireStyles
и @livewireScripts
. Livewire будет делать инъекцию этих скриптов и стилей автоматически.
Но если вдруг Вам такой подход не нравится, то в config можете переключить false и добавлять ассеты самостоятельно, по старинке:
'inject_assets' => false,
Прежде чем мы пойдем далее, сразу скажу о понравившемся мне моменте — теперь wire:model
по умолчанию с параметром defer
. И это круто, так как до этого каждый раз, объявляя wire:model
, приходилось добавлять .defer
. Так как апдейт на каждое изменение практически никогда не был нужен (или только в редких случаях). Но теперь для этих редких случаев уже нам потребуется определенные параметры как live
, либо blur
.
Wire: Navigate
Чтобы подготовить для вас этот обзор как можно более качественнее, я заодно начал реализацию проекта из экосистемы CutCode с готовыми Livewire компонентами, но уже на Livewire 3 и Volt. Как раз на примере этого проекта мы будем с вами играться.
Итак, следующее на что стоит обратить внимание в Livewire 3 — это новая фича wire:navigate
, мы можем ставить этот параметр на ссылке и у нас будет переход по страницам без перезагрузки:
На самом деле ничего сверхъестественного нет, попытка эмуляции single page application. Как видим у меня на логотипе стоит wire:navigate
:
Если мы с вами по нему нажмем, то у нас перезагрузки не будет, но при этом придет HTML и основная страница заменится. Есть еще определенный интересный модификатор — называется он wire:navigate.hover
:
В этом случае у нас с вами HTML-содержимое страницы придет без клика, а просто при наведении.
Forms
Далее важное изменение — это Forms (формы). Классы с формами и все что касается форм теперь вынесено в отдельный слой.
У некоторых возникли вопросы из-за того что старый подход с Model binding теперь не подходит. Давайте взглянем на Upgrade Guide и перейдем в секцию Eloquent model binding и ранее в Livewire компоненте нам было достаточно указать свойства и сделать type hint модели и далее черезwire:model
, через точку, обращаться к полям этой модели.
Теперь так работать не будет. Так как эту логику необходимо выносить Form Objects, о которых мы сейчас с вами поговорим. Но, если вы хотите работать по старинке, то в конфиге вы можете переключить флаг
'legacy_model_binding' => true,
и старый подход будет работать. Но я не рекомендую вам его использовать и предлагаю переходить на новую концепцию. Рано или поздно этот legacy подход отключат.
Вернемся к формам.
Попробуем создать форму, используя artisan команду из документации.
php artisan livewire:form UserForm
И давайте также создадим компонент, где будем с вами эту форму юзать:
Давайте откроем созданный компонент. Пока что пустой:
Чтобы нам не тратить время на Blade, скопируем код формы из документации:
Вставляем в наш user.blade.php
:
Новая особенность Livewire 3 — у формы не нужно по умолчанию указывать prevent
, это будет по дефолту (так же как с wire:model
— теперь не нужно по умолчанию указывать defer
).
Как видим, в данном случае ничего у нас не изменилось. Только появилась определенная форма, с которой мы сейчас будем играться. Давайте оживим немного форму и в коде который мы скопировали из документации изменим form.title
на form.name
и form.content
на form.email
:
Далее, друзья, давайте смотреть на изменения. Во-первых livewire компоненты и форма теперь у нас располагаются в директории app, а не в http.
Формы в директории Forms, а компоненты соответственно в директории Livewire. Вот наш компонент User:
Как мы бы действовали с вами ранее, когда еще никаких форм не было? Верно, мы бы прямо здесь (в User.php) с вами создавали свойства. Если бы использовали Legacy подход, то сделали бы type hint c моделью и все было бы хорошо.
Но давайте сделаем как положено, на отдельных свойствах — $name
и $email
:
В старой версии Livewire мы в User.php
сделали бы метод Save, затем добавили валидацию и указали бы правила. После этого — создаем пользователя (User::create
), передав ему name и email:
Но, друзья, с приходом форм мы просто переносим эту логику и ответственность на сами формы давайте это сделаем и перенесем часть кода в наш UserForm.php
:
соответственно validate
из User.php
так же уберем:
В самой форме мы можем использовать как и по старинке наши правила — rules
:
В целом, форма готова. Далее мы просто объявляем публичное свойство public UserForm $form
, а в методе save()
указываем $this->form->validate
:
Но мы можем сделать еще лучше и вынесем логику в форму:
А в самом компоненте у нас будет $this->form->storе()
:
Давайте выведем компонент в app.blade.php
, чтобы было на что посмотреть:
Вернемся на главную страницу, видим, что у нас образовалась форма
Нажмем Save и видим, что у нас срабатывает валидация!
Мы вынесли логику в форму и у нас все работает как положено.Чтобы форма выглядела по приятнее навел немного красоты:
Следующий вопрос — это как биндить модель в эту форму? Раньше мы просто биндели в компонент, делали type hint определенного свойства и далее через точку делали wire:model
уже внутри компонента.
C формой дела обстоят немного иначе — это частый вопрос при переходе на третью версию. Давайте его с Вами рассмотрим. Откроем User.php
и добавим по старинке метод mount()
. Укажем, что у нас обязательный параметр модель User (\App\Models\User $user
).
Давайте в форме сделаем метод setUser(User $user)
и внутри наполним данными name и email для наглядности:
А далее нам нужно ее установить (засетить) форму, то есть сделать $this->form->setUser ($user)
Далее открываем app.blade.php
(там, где мы рендерим компонент) и добавляем livewire:user
:
Давайте посмотрим, что у нас из этого получилось. Видим что у нас бинд срабатывает и данные подставляются:
Теперь если очистим нажмем save, видим, что и валидация у нас работает:
PHP attributes
Дальше вопрос валидации. Здесь есть некоторые изменения — старый подход через методы rules()
, messages()
работает, но также для нашего удобства добавили PHP атрибуты. Да Livewire просто пестрит PHP атрибутами, они практически везде и сейчас постепенно мы их с вами будем осваивать.
Давайте уберем пока что метод rules()
из UserForm.php
и далее с помощью атрибута Rule
из Livewire будем добавлять необходимые нам правила — required, string и min со значением 3. То же самое продублируем и на e-mail:
Вернемся в браузер, обновляемся, попробуем очистить поля, нажмем save и видим что у нас все так же работает только теперь через PHP атрибуты.
Добавлять атрибуты можно другим способом, Вы можете перейти в раздел валидация и более углубленно взглянуть на те возможности, которые дают нам PHP атрибуты. Есть дополнительные свойства, прямо в атрибуте можно указать и сообщения валидации, а также переводить сообщения или нет, можно указывать в виде массива, добавлять туда классы. Подход через PHP attributes присутствует, и если он вам больше нравится, то обязательно используйте. Если нет, то можете пользоваться старым подходом через rules(): array
:
Кстати, большинство новичков ищут в документации что-либо о Form Requests. У нас есть форма и хочется в ней применять стандартные Form Requests, но если вы пройдетесь по документации Livewire, то вы не увидите ни одного слова о Form Requests. Но если вдруг вы хотите их использовать, то можете их интегрировать прямо в метод rules()
.
Давайте создадим какой-либо Form Requests, так как в проекте который используется для примера их нет.
И если бы я хотел интегрировать сюда в Form Requests, я бы сделал следующее и в целом это рабочий подход для решения.
Друзья, раз уж мы с вами затронули тему PHP атрибутов в Livewire, то давайте ее продолжим и посмотрим еще на несколько таких атрибутов. Для этого переместимся в компонент для главной страницы (Home.php
), здесь full page компоненты и поэтому я прямо в компоненте могу с помощью PHP атрибутов задавать title страницы, менять layout. Как это делается? Например, у метода render()
я могу добавить атрибут title и указать что-либо (Hello):
Давайте вернемся назад, обновимся и видим, что заголовок поменялся на тот который я указал:
Тоже самое можно было бы сделать не у метода render()
, а у класса Home и все так же будет работать:
И в таком же стиле я могу менять layout, используя атрибут Layout где указываем его расположение и это будет работать.
Давайте сделаем ошибку, чтобы убедиться что действительно работает — укажем components.layouts.app2
— такого layout уже нет и получаем соответствующее уведомление об ошибке:
Следующий интересный атрибут — Locked, давайте посмотрим как он выглядит и как он работает. На определенное свойство мы можем его с вами повесить и далее в клиентской части уже не сможем его изменять.Давайте попробуем и повесим его на поле для ввод имени пользователя, попытаемся изменить (что-либо ввести в поле) нажимаю Save и у меня появляется исключение — что Locked Property мы менять с вами не можем.
События
Друзья, следующее что мы с вами рассмотрим — это события (events) и как по мне с атрибутами это супер интересный подход. Давайте взглянем как это работает? К примеру во время сохранения (User.php
) мы с вами будем вызывать событие, пусть это будет form-store
. Закомментируем 22 строку и добавим метод onFormStore()
в котором для наглядности и простоты сделаем Dump and Die (dd). Теперь укажем через атрибут On на какое событие будет вызываться этот метод, соответственно form-store
. То есть в методе save()
события дергаем, а в onFormStore()
указываем какое (событие) будет при этом вызываться.
И таких listener может быть несколько.
Проверим в браузере, нажимаем на save и видим что dump & die срабатывает:
Мы даже с вами можем взять метод onFormStore()
из User.php
и вынести его в какой-то другой компонент, например разместим на главной странице Home.php
. Проверим в браузере — событие снова вызовется.
Также интересный атрибут, когда мы дергаем какой-либо метод из Blade компонента, то у нас происходит ReRender. Приходят данные и компонент рендерится по-новому. Если на определенные методы такое поведение нам необходимо исключить, то мы можем добавить атрибут Renderless и в таком случае, когда мы будем дергать этот метод, компонент перерисовываться не будет:
Вычисляемые свойства
Следующий интересный раздел в Livewire 3 — это computed properties, вычисляемые свойства. Необходимы они нам для контроля над производительностью. Если мы вешаем атрибут Computed над каким-то методом, то он нам уже будет доступен через $this->
внутри этого компонента и внутри Blade-компонента. Но при этом в процессе реквеста вызываться он будет только единожды:
Независимо от того, что у нас здесь запрос к базе, livewire закэширует содержимое и мы при неоднократном обращении к одному и тому же методу будем всегда возвращать одно и то же значение. Давайте взглянем как это работает? Будем использовать компонент User.php
, создадим свойство u()
и добавим ему компонент [Computed], а в содержании укажем о возвращении рандомного юзера. И давайте пару раз задампим его. Во-первых в методе render()
указанном в User.php
, здесь обратите внимание свойства не создавалось, но обращение как к свойству u.
Во-вторых, то же самое в blade-компоненте user.blade.php
. Также обращаю ваше внимание на то, что в blade-компоненте мы также вызываем через $this->
:
Как видите в компоненте мы вызываем id пользователя и тоже самое внутри Blade шаблона. Но при этом, отправляем всего один request цикл, один запрос:
Также мы можем дополнительно кэшировать, указав в computed cache:true
:
Проверим. При каждом обновлении будет приходить id первого пользователя:
Вложенные компоненты
Передвигаемся в более интересную тему это вложенные компоненты (nesting components). По сути полностью переписанный подход в Livewire 3 и теперь он работает намного круче и есть реактивность. Давайте взглянем на практике — создадим еще один с вами компонент, который будет у нас внутри компонента User. Давайте назовем его UserInner
:
Сразу его откроем и вызовем его в нашем компоненте user.blade.php
, как раз он станет у нас вложенным и сразу давайте передадим в него форму:
Откроем UserInner.php
и создадим метод mount()
:
Давайте откроем view user-inner.blade.php
и в ней соответственно выведем что-то из формы, например name и заодно сразу добавим Input, чтобы добавить wire:model
:
Посмотрим в браузере что у нас при этом получается. Обновляемся, и видим, что у нас есть родительская форма и есть дочерняя:
И давайте заодно добавим кое-что в родительское поле. Укажем, что у нас wire:model.live
— чтобы при вводе сразу менялась свойство формы:
Возвращаемся в браузер и меняем содержимое поля. Видим что у нас в родительском компоненте значение изменяется, но при этом в дочернем ничего не меняется:
Так работал Livewire раньше, никакой реактивности не было. У нас рендерился только родительский компонент. Давайте заодно взглянем на запросы, которые у нас отправляются. Если мы с вами будем менять содержимое родительского поля,
Видим что уходит апдейт и у нас только один компонент изменяется и приходит только его HTML:
Чтобы менять и дочерние, добавить реактивности которую мы ожидаем, нам нужно с вами перейти в UserInner.php
и на форму повесить атрибут [Reactive]. Возвращаемся в браузер, обновляемся и теперь при вводе значений в поля формы у нас меняется и содержимое дочернего поля и его wire:model
и значение:
Посмотрим в инструментах разработчика на вкладу Network, что там происходит. При вводе в родительское поле видим, что в update у нас массив с двумя компонентами — и родитель и все его дочерние:
И напоследок еще немного магии по вложенным компонентам. Нам также доступна переменная $parent
с помощью которой мы можем обратиться к объекту родительского компонента. Давайте попробуем: $parent->form->name
Смотрим в браузере — вводим значения в поле родительской формы и это же значение появляется и в дочерней. Работает!
Polling
Друзья, также хочу вам показать раздел polling
и wire:poll
который был и до этого. Многие думают, что у нас происходит живое обновление по типу веб-сокетов. Даже в документации написано технология как web-сокеты, но ничего общего с веб-сокетами это не имеет. Конечно штука интересная, но в ней нет ничего необычного. На самом деле это просто запросы по тайм-ауту, поэтому рекомендую относиться к этой директиве крайне аккуратно. Давайте добавим на компонент user-inner.blade.php wire:poll:
Посмотрим в браузере, на вкладку Network инструментов разработчика:
Как видите по тайм-ауту происходит запрос, простой запрос, никакие не веб-сокеты и в ответе мы получаем данные по компоненту. Если что-то там на сервере изменилось, то само собой придут данные. Но обратите внимание как мы сейчас спамим запросами к серверу, поэтому крайне аккуратно относитесь к этой директиве. Если у вас их будет множество, все они будут спамить, решение будет не из лучших, а под капотом нет никакой магии, простой тайм-аут:
Lazy Components
Следующее что я хочу вам показать это крайне интересная фича, а именно Lazy компоненты — ленивые компоненты, которые сразу у нас не грузятся, а когда у нас уже пришел ответ, уже после этого отправляется запрос, не мешая нашему первому рендеру. Для этого достаточно добавить lazy:
Представим, у нас есть какие-то компоненты, какие-то сложные метрики с множеством запросов. Не хочется чтобы наша страница грузилась несколько секунд перед тем как отдать ее пользователю. Мы добавляем lazy, а далее переходим в компонент и вешаем на него placeholder
. Это то, что у нас появится прежде чем мы запустим рендер компонента. И давайте чтобы посмотреть как это работает сделаем sleep
продолжительностью 3 секунды:
Вернемся на в браузер и обновимся:
Видим, что второго компонента пока что нет, есть надпись «loading», идёт загрузка и только по прошествию указанного времени (мы указали 3 сек.), появился наш дочерний элемент:
Отличная штука обязательно берем на вооружение.
LiveWire Volt
Настало время для обзора Volt. На самом деле это тот же самый Livewire, он располагается в документации Livewire и создан на его основе. Единственное его отличие, что все находится в одном blade файле. Никакого Livewire класса, Livewire класс-компонента больше нет. И сверху в теге PHP мы организуем всю backend логику, а дальше у нас идет frontend. Разработчики вдохновлялись Vue Framework.
Устанавливается Volt просто, сперва устанавливаем Livewire, затем Volt. Делается это командой volt:Install
, которая добавляет сервис провайдер — VoltServiceProvider
.
Внутри мы указываем где у нас располагаются Livewire компоненты, где будут располагаться страница для Folio.
Далее, чтобы создать компонент используем команду make:volt
и далее название, как make:livewire
, только ключевое слово volt. Во всем остальном обязательно изначально вам нужно знать livewire, а уже после использовать Volt. Все то же самое перекочевало сюда в виде функций-хелперов. Единственное с чем может быть замешательство — это то, что входные параметры передаются при помощи функции state. Если нам необходим какой-то экшен, то вызываем его анонимной функцией.
Также нам говорят что в целом можем использовать и классы, но также сверху blade-файла, внутри. Как по мне — это выглядит крайне убого! Если на то что мы пишем логику прямо в Blade в целом в рамках Volt можно закрыть глаза на каких-то небольших компонентах, но добавлять анонимные классы, считаю что выглядит ужасно.
Окей, со state
мы разобрались. Если нам необходим метод Mount, то пользуемся функцией mount()
:
Хотим изменить layout — пользуемся функцией layout()
:
Пользуемся функцией title для заголовка:
Как я говорил ранее все то же самое есть и здесь. Продолжим листать документацию и видим пример использования locked()
через state()
. Мы использовали его ранее:
То же самое с reactive, то же самое с computed. Это тот же самый Livewire, просто изменился подход. Все теперь организовывается в Blade-компоненте. Можем продолжать листать документацию до бесконечности и ничего нового мы с вами здесь не увидим «сахар», «сахар», «сахар» в виде дополнительных функций, которые делают в точности все то же самое, что мы делали в стандартном Livewire классе.
Давайте все же немного поиграемся и создадим с volt-компонент counter:
Отлично, видим что Blade компонент создан и больше ничего. Вот он так выглядит по дефолту:
Дальше нам предлагается со всем этим взаимодействовать. Давайте создадим определенный state, пусть будет count и по дефолту сделаем 0. Давайте попробуем вывести. Насколько я помню необходимо использовать $this
.
Вернемся в браузер, обновимся и видим нолик, это state и то значение, которое мы здесь задаем:
Давайте также добавим Action. Пусть будет кнопка которая у нас будет инкрементировать этот счетчик count. Используем wire:click
. Добавим инкремент, чтобы создать экшен, достаточно создать переменную которая у нас будет анонимная функция. На самом деле уже понимаю, что не очень удобно работать так как PHPStorm мне не подсказывает что в рамках $this->
контекста у нас есть count и приходится все писать самому:
Посмотрим в браузер, попробуем нажать в область отображения счетчика и видим что счетчик у нас меняется:
Давайте еще немного поиграем с Volt, заодно в очередной раз разберем тему computed properties — вычисляемых свойств, так как она мне очень нравится. Давайте создадим переменную $test
со значением computed(function())
, далее callback и внутри мы вернем count. И давайте выведем наш test, только он у нас computed:
Вернемся в браузер, нажимаем на »+», пытаемся инкрементировать. И первая переменная и вторая ведут себя абсолютно идентично:
Но давайте добавим computed метод persist
, который у нас будет кэшировать (запоминать):
Вернемся в браузер. При первом заходе у нас отображается значение 00, а дальше мы с вами инкрементируем, но computed теперь никак не меняется, так как это значение у нас закэшировано и повторно ничего не происходит:
Давайте для наглядности изменим persist, добавив seconds и укажем чтобы он у нас хранился всего 2 секунды.
Вернемся в браузер, обновимся, инкрементируем и видим, что не каждое изменение, но раз в 2 секунды у нас приходят измененные значения:
Друзья, как видите ковырять Volt мы можем с вами сколько угодно долго, но никаких отличий от Livewire, кроме подхода «все в одном blade-файле», больше нет. В целом лично мне Livewire 3 дал так называемое второе дыхание, хочется что-то на нем написать, использовать, делать ролики на мой Youtube канал, но опять-таки только в том случае если и вам интересно. Поэтому обязательно напишите в комментариях стоит ли продолжать делать уроки по Livewire?
Спасибо за уделенное внимание. Данил Щуцкий, автор проекта CutCode.