2.1. Компоненты

Компоненты — это строительные блоки вашего Livewire-приложения. Они объединяют состояние и поведение, создавая переиспользуемые элементы интерфейса для фронтенда. В этом разделе мы рассмотрим основы создания и рендеринга компонентов.

1. Создание компонентов

Компонент Livewire — это просто PHP-класс, который наследуется от базового класса Livewire\Component. Вы можете создать файлы компонента вручную или с помощью следующей Artisan-команды:

php artisan make:livewire CreatePost

Если вы предпочитаете использовать имена в стиле kebab-case, это также возможно:

php artisan make:livewire create-post

После выполнения этой команды Livewire создаст два новых файла в вашем приложении. Первый — это класс компонента: app/Livewire/CreatePost.php

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
public function render()
{
return view('livewire.create-post');
}
}

Второй — это Blade-представление компонента: resources/views/livewire/create-post.blade.php

<div>
{{-- ... --}}
</div>

Вы можете использовать синтаксис пространств имен или точечную нотацию для создания компонентов в подкаталогах. Например, следующие команды создадут компонент CreatePost в подкаталоге Posts:

php artisan make:livewire Posts\\CreatePost
php artisan make:livewire posts.create-post

1.1. Встроенные компоненты

Если ваш компонент достаточно небольшой, вы можете создать встроенный компонент. Встроенные компоненты — это однострочные компоненты Livewire, чей шаблон представления содержится непосредственно в методе render(), а не в отдельном файле:

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
public function render()
{
return <<<'HTML'
<div>
{{-- Ваш шаблон Blade размещается здесь... --}}
</div>
HTML;
}
}

Вы можете создать встроенные компоненты, добавив флаг --inline к команде make:livewire:

php artisan make:livewire CreatePost --inline

Исключение метода render

Чтобы уменьшить шаблонный код в ваших компонентах, вы можете полностью опустить метод render(). В этом случае Livewire будет использовать свой собственный базовый метод render(), который возвращает представление с традиционным именем, соответствующим вашему компоненту:

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
//
}

Если приведенный выше компонент будет отображен на странице, Livewire автоматически определит, что для его отображения следует использовать шаблон livewire.create-post.

1.3. Настройка заготовок компонентов

Вы можете настроить файлы (или заготовки), которые Livewire использует для создания новых компонентов, выполнив следующую команду:

php artisan livewire:stubs

Это создаст четыре новых файла в вашем приложении:

  • stubs/livewire.stub — используется для создания новых компонентов
  • stubs/livewire.inline.stub — используется для создания встроенных компонентов
  • stubs/livewire.test.stub — используется для создания тестовых файлов
  • stubs/livewire.view.stub — используется для создания представлений компонентов

Несмотря на то, что эти файлы находятся в вашем приложении, вы все равно можете использовать команду Artisan make:livewire, и Livewire автоматически будет использовать ваши пользовательские заготовки при создании файлов.

2. Установка свойств

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

Чтобы добавить свойство к компоненту Livewire, объявите публичное свойство в классе вашего компонента. Например, давайте создадим свойство $title в компоненте CreatePost:

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
public $title = 'Post title...';
 
public function render()
{
return view('livewire.create-post');
}
}

2.1. Доступ к свойствам в представлении

Свойства компонента автоматически становятся доступными в представлении Blade этого компонента. Вы можете ссылаться на них, используя стандартный синтаксис Blade. Здесь мы выведем значение свойства $title:

<div>
<h1>Title: "{{ $title }}"</h1>
</div>

Отображаемый результат этого компонента будет следующим:

<div>
<h1>Title: "Post title..."</h1>
</div>

2.2. Передача дополнительных данных в представление

Помимо доступа к свойствам из представления, вы можете явно передавать данные в представление из метода render(), как это обычно делается в контроллере. Это может быть полезно, если вы хотите передать дополнительные данные, не сохраняя их сначала как свойства — поскольку свойства имеют определенные особенности, связанные с производительностью и безопасностью.

Чтобы передать данные в представление через метод render(), вы можете использовать метод with() у экземпляра представления. Например, предположим, вы хотите передать имя автора поста в представление. В данном случае автором поста является текущий аутентифицированный пользователь:

<?php
 
namespace App\Livewire;
 
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
 
class CreatePost extends Component
{
public $title;
 
public function render()
{
return view('livewire.create-post')->with([
'author' => Auth::user()->name,
]);
}
}

Теперь вы можете получить доступ к свойству $author из представления Blade компонента:

<div>
<h1>Title: {{ $title }}</h1>
 
<span>Author: {{ $author }}</span>
</div>

2.3. Добавление wire:key в циклы@foreach

При итерации данных в шаблоне Livewire с использованием @foreach необходимо добавить уникальный атрибут wire:key к корневому элементу, создаваемому в цикле.

Если в цикле Blade отсутствует атрибут wire:key, Livewire не сможет правильно сопоставить старые элементы с их новыми позициями при изменении цикла. Это может вызвать множество сложных для диагностики проблем в вашем приложении.

Например, если вы перебираете массив постов, вы можете установить атрибут wire:key равным ID поста:

<div>
@foreach ($posts as $post)
<div wire:key="{{ $post->id }}">
<!-- ... -->
</div>
@endforeach
</div>

Если вы перебираете массив, который отрисовывает компоненты Livewire, вы можете установить ключ как атрибут компонента :key() или передать ключ в качестве третьего аргумента при использовании директивы @livewire.

<div>
@foreach ($posts as $post)
<livewire:post-item :$post :key="$post->id">
 
@livewire(PostItem::class, ['post' => $post], key($post->id))
@endforeach
</div>

2.4. Привязка полей ввода к свойствам

Одной из самых мощных возможностей Livewire является "привязка данных": способность автоматически синхронизировать свойства с элементами формы на странице.

Давайте привяжем свойство $title из компонента CreatePost к текстовому полю с помощью директивы wire:model:

<form>
<label for="title">Title:</label>
 
<input type="text" id="title" wire:model="title">
</form>

Все изменения, внесенные в текстовое поле, будут автоматически синхронизированы с свойством $title в вашем компоненте Livewire.

"Почему мой компонент не обновляется в реальном времени, пока я печатаю?"

Если вы попробовали это в вашем браузере и удивляетесь, почему заголовок не обновляется автоматически, это связано с тем, что Livewire обновляет компонент только тогда, когда выполняется "действие" — например, при нажатии кнопки отправки, а не когда пользователь вводит текст в поле. Это сокращает количество сетевых запросов и улучшает производительность. Чтобы включить "живое" обновление во время ввода, используйте wire:model.live. Узнайте больше о привязке данных.

Свойства Livewire являются чрезвычайно мощными и важным понятием, которое необходимо понять. Для получения дополнительной информации ознакомьтесь с документацией по свойствам Livewire.

3. Вызов действий

Действия — это методы внутри вашего Livewire-компонента, которые обрабатывают взаимодействия с пользователем или выполняют определённые задачи. Они часто используются для обработки нажатий кнопок или отправки форм на странице.

Чтобы узнать больше о действиях, давайте добавим действие save в компонент CreatePost:

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use App\Models\Post;
 
class CreatePost extends Component
{
public $title;
 
public function save()
{
$post = Post::create([
'title' => $this->title
]);
 
return redirect()->to('/posts')
->with('status', 'Post created!');
}
 
public function render()
{
return view('livewire.create-post');
}
}

Далее вызовем действие save из Blade-шаблона компонента, добавив директиву wire:submit к элементу <form>:

<form wire:submit="save">
<label for="title">Title:</label>
 
<input type="text" id="title" wire:model="title">
 
<button type="submit">Save</button>
</form>

Когда кнопка "Сохранить" будет нажата, метод save() в вашем Livewire-компоненте будет выполнен, и компонент будет повторно отрендерен.

Чтобы продолжить изучение действий в Livewire, посетите документацию по действиям.

4. Рендеринг компонентов

Существует два способа рендеринга Livewire-компонента на странице:

  1. Включить его в существующий Blade-шаблон
  2. Привязать его напрямую к маршруту как компонент на всю страницу

Давайте рассмотрим первый способ рендеринга вашего компонента, так как он проще второго.

Вы можете включить Livewire-компонент в свои Blade-шаблоны, используя синтаксис <livewire:component-name />:

<livewire:create-post />

Если класс компонента находится глубже в каталоге app/Livewire/, вы можете использовать символ . для указания вложенности каталогов. Например, если мы предполагаем, что компонент расположен по пути app/Livewire/EditorPosts/CreatePost.php, его можно отрендерить следующим образом:

<livewire:editor-posts.create-post />
Вы должны использовать kebab-case

Как вы можете видеть в приведённых выше примерах, вы должны использовать kebab-case версию имени компонента. Использование версии имени в StudlyCase (<livewire:CreatePost />) недопустимо и не будет распознано Livewire.

4.1. Передача данных в компоненты

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

Чтобы передать начальное значение свойству $title компонента CreatePost, вы можете использовать следующий синтаксис:

<livewire:create-post title="Initial Title" />

Если вам нужно передать динамические значения или переменные в компонент, вы можете писать PHP-выражения в атрибутах компонента, добавляя двоеточие перед атрибутом:

Данные, переданные в компоненты, принимаются через хук жизненного цикла mount() в виде параметров метода. В этом случае, чтобы присвоить параметр $title свойству, вы должны написать метод mount(), как показано ниже:

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
public $title;
 
public function mount($title = null)
{
$this->title = $title;
}
 
// ...
}

В этом примере свойство $title будет инициализировано со значением "Initial Title".

Вы можете рассматривать метод mount() как конструктор класса. Он выполняется при начальной загрузке компонента, но не при последующих запросах на странице. Вы можете узнать больше о mount() и других полезных хуках жизненного цикла в документации по жизненному циклу.

Чтобы уменьшить шаблонный код в ваших компонентах, вы можете опустить метод mount(), и Livewire автоматически установит любые свойства вашего компонента, имена которых совпадают с переданными значениями:

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
public $title;
 
// ...
}

Это фактически то же самое, что и присвоение значения $title внутри метода mount().

Эти свойства не являются реактивными по умолчанию

Свойство $title не будет автоматически обновляться, если внешнее значение :title="$initialValue" изменится после начальной загрузки страницы. Это частая причина путаницы при использовании Livewire, особенно для разработчиков, работавших с JavaScript-фреймворками, такими как Vue или React, и предполагающих, что эти "параметры" ведут себя как "реактивные пропсы" в этих фреймворках. Но не беспокойтесь, Livewire позволяет включить реактивность для ваших пропсов.

5. Полностраничные компоненты

Livewire позволяет привязывать компоненты напрямую к маршруту в вашем Laravel-приложении. Такие компоненты называются "компонентами на всю страницу". Вы можете использовать их для создания автономных страниц с логикой и представлениями, полностью инкапсулированными внутри Livewire-компонента.

Чтобы создать полностраничный компонент, определите маршрут в файле routes/web.php и используйте метод Route::get(), чтобы сопоставить компонент с конкретным URL-адресом. Например, предположим, вы хотите отобразить компонент CreatePost по маршруту /posts/create.

Вы можете добиться этого, добавив следующую строку в ваш файл routes/web.php:

use App\Livewire\CreatePost;
 
Route::get('/posts/create', CreatePost::class);

Теперь, когда вы перейдёте по пути /posts/create в вашем браузере, компонент CreatePost будет отображён как полностраничный компонент.

5.1. Файлы макета

Помните, что полностраничные компоненты используют макет вашего приложения, который обычно определяется в файле resources/views/components/layouts/app.blade.php.

Вы можете создать этот файл, если он ещё не существует, выполнив следующую команду:

php artisan livewire:layout

Эта команда создаст файл с именем resources/views/components/layouts/app.blade.php.

Убедитесь, что вы создали Blade-файл в этом расположении и добавили в него плейсхолдер {{ $slot }}:

<!-- resources/views/components/layouts/app.blade.php -->
 
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) {{">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 
<title>{{ $title ?? 'Page Title' {{</title>
</head>
<body>
{{ $slot {{
</body>
</html>

5.1.1. Глобальная конфигурация макета

Чтобы использовать пользовательский макет для всех ваших компонентов, вы можете установить ключ layout в файле config/livewire.php на путь к вашему макету относительно resources/views. Например:

'layout' => 'layouts.app',

С указанной выше конфигурацией Livewire будет отображать полностраничные компоненты внутри файла макета: resources/views/layouts/app.blade.php.

5.1.2. Конфигурация макета для каждого компонента

Чтобы использовать другой макет для конкретного компонента, вы можете добавить атрибут Livewire #[Layout] над методом render() компонента, передав в него относительный путь к вашему пользовательскому макету:

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Layout;
use Livewire\Component;
 
class CreatePost extends Component
{
// ...
 
#[Layout('layouts.app')]
public function render()
{
return view('livewire.create-post');
}
}

Или, если вы предпочитаете, вы можете использовать этот атрибут над объявлением класса:

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Layout;
use Livewire\Component;
 
#[Layout('layouts.app')]
class CreatePost extends Component
{
// ...
}

PHP-атрибуты поддерживают только литеральные значения. Если вам нужно передать динамическое значение или вы предпочитаете этот альтернативный синтаксис, вы можете использовать метод ->layout() в методе render() компонента:

public function render()
{
return view('livewire.create-post')
->layout('layouts.app');
}

Кроме того, Livewire поддерживает использование традиционных Blade-файлов макета с директивой @extends.

Учитывая следующий файл макета:

<body>
@yield('content')
</body>

Вы можете настроить Livewire для его использования с помощью ->extends() вместо ->layout():

public function render()
{
return view('livewire.show-posts')
->extends('layouts.app');
}

Если вам нужно настроить @section для использования компонентом, вы можете сделать это с помощью метода ->section():

public function render()
{
return view('livewire.show-posts')
->extends('layouts.app')
->section('body');
}

5.2. Установка заголовка страницы

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

Чтобы установить пользовательский заголовок страницы для полностраничного компонента, сначала убедитесь, что ваш файл макета включает динамический заголовок:

<head>
<title>{{ $title ?? 'Page Title' }}</title>
</head>

Затем добавьте атрибут #[Title] над методом render() вашего Livewire-компонента и передайте ему заголовок страницы:

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Title;
use Livewire\Component;
 
class CreatePost extends Component
{
// ...
 
#[Title('Create Post')]
public function render()
{
return view('livewire.create-post');
}
}

Это установит заголовок страницы для Livewire-компонента CreatePost. В этом примере заголовок страницы будет "Create Post" при отображении компонента.

Если вы предпочитаете, вы можете использовать этот атрибут над объявлением класса:

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Title;
use Livewire\Component;
 
#[Title('Create Post')]
class CreatePost extends Component
{
// ...
}

Если вам нужно передать динамический заголовок, например, заголовок, использующий свойство компонента, вы можете использовать метод ->title() в методе render() компонента:

public function render()
{
return view('livewire.create-post')
->title('Create Post');
}

5.3. Установка дополнительных слотов в файле макета

Если ваш файл макета содержит именованные слоты помимо $slot, вы можете задать их содержимое в Blade-шаблоне, определив <x-slot> за пределами корневого элемента. Например, если вы хотите иметь возможность задавать язык страницы для каждого компонента отдельно, вы можете добавить динамический слот $lang в открывающий HTML-тег в вашем файле макета:

<!-- resources/views/components/layouts/app.blade.php -->
 
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', $lang ?? app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 
<title>{{ $title ?? 'Page Title' }}</title>
</head>
<body>
{{ $slot }}
</body>
</html>

Затем, в шаблоне вашего компонента, определите элемент <x-slot> за пределами корневого элемента:

<x-slot:lang>ru</x-slot> // Этот компонент на русском языке
 
<div>
// Русский контент будет здесь...
</div>

5.4. Доступ к параметрам маршрута

При работе с полностраничными компонентами вам может понадобиться получить доступ к параметрам маршрута внутри вашего Livewire-компонента.

Для примера, сначала добавьте маршрут с параметром в файл routes/web.php:

use App\Livewire\ShowPost;
 
Route::get('/posts/{id}', ShowPost::class);

Здесь мы задали маршрут с параметром id, который обозначает идентификатор записи.

Далее обновите свой Livewire-компонент, чтобы он принимал параметр маршрута в методе mount():

<?php
 
namespace App\Livewire;
 
use App\Models\Post;
use Livewire\Component;
 
class ShowPost extends Component
{
public Post $post;
 
public function mount($id)
{
$this->post = Post::findOrFail($id);
}
 
public function render()
{
return view('livewire.show-post');
}
}

В этом примере, так как имя параметра $id совпадает с параметром маршрута {id}, при переходе по адресу /posts/1 Livewire передаст значение «1» в $id.

5.5. Использование привязки моделей маршрута

Привязка моделей маршрута в Laravel позволяет автоматически получать экземпляры моделей Eloquent из параметров маршрута.

После того как вы определите маршрут с параметром модели в файле routes/web.php:

use App\Livewire\ShowPost;
 
Route::get('/posts/{post}', ShowPost::class);

Теперь вы можете принять параметр модели маршрута через метод mount() вашего компонента:

<?php
 
namespace App\Livewire;
 
use App\Models\Post;
use Livewire\Component;
 
class ShowPost extends Component
{
public Post $post;
 
public function mount(Post $post)
{
$this->post = $post;
}
 
public function render()
{
return view('livewire.show-post');
}
}

Livewire понимает, что нужно использовать «привязку моделей маршрута», потому что перед параметром $post в методе mount() указана типизация Post.

Как и раньше, вы можете сократить шаблонный код, опустив метод mount():

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use App\Models\Post;
 
class ShowPost extends Component
{
public Post $post;
 
public function render()
{
return view('livewire.show-post');
}
}

Свойство $post будет автоматически установлено в модель, привязанную через параметр маршрута {post}.

5.6. Модификация ответа

В некоторых случаях вам может понадобиться изменить ответ и установить собственный заголовок. Вы можете получить доступ к объекту ответа, вызвав метод response() у представления и передав замыкание для его изменения:

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use Illuminate\Http\Response;
 
class ShowPost extends Component
{
public function render()
{
return view('livewire.show-post')
->response(function(Response $response) {
$response->header('X-Custom-Header', true);
});
}
}

6. Использование JavaScript

Во многих случаях встроенных средств Livewire и Alpine бывает недостаточно, чтобы реализовать задуманное внутри ваших Livewire-компонентов.

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

6.1. Выполнение скриптов

Livewire предоставляет удобную директиву @script, которая, оборачивая элемент <script>, выполнит указанный JavaScript при инициализации вашего компонента на странице.

Вот пример простого @script, который с помощью JavaScript-функции setInterval() обновляет ваш компонент каждые две секунды:

@script
<script>
setInterval(() => {
$wire.$refresh()
}, 2000)
</script>
@endscript

Вы заметите, что внутри тега <script> мы используем объект $wire для управления компонентом. Livewire автоматически делает этот объект доступным внутри любых @script. Если вы не знакомы с $wire, вы можете узнать о нём больше в следующей документации:

6.2. Загрузка ресурсов

Помимо разовых @script, Livewire предоставляет удобный инструмент @assets для простого подключения любых скриптов и стилей на страницу.

Он также гарантирует, что указанные ресурсы будут загружены только один раз на страницу в браузере, в отличие от @script, который выполняется при каждой инициализации нового экземпляра этого Livewire-компонента.

Вот пример использования @assets для подключения библиотеки выбора даты Pikaday и её инициализации внутри вашего компонента с помощью @script:

<div>
<input type="text" data-picker>
</div>
 
@assets
<script src="https://cdn.jsdelivr.net/npm/pikaday/pikaday.js" defer></script>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/pikaday/css/pikaday.css">
@endassets
 
@script
<script>
new Pikaday({ field: $wire.$el.querySelector('[data-picker]') });
</script>
@endscript
Использование @script и @assets внутри Blade-компонентов

Если вы используете Blade-компоненты для выделения частей вашей разметки, вы также можете применять внутри них @script и @assets, даже если в одном Livewire-компоненте находится несколько Blade-компонентов. Однако в настоящий момент @script и @assets поддерживаются только в контексте Livewire-компонента, то есть если вы используете такой Blade-компонент вне Livewire, эти скрипты и ресурсы не будут загружены на страницу.