Обзор LiveWire 3 и Volt

Обзор 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.