Livewire позволяет строить реактивные интерфейсы на Laravel, не уходя в SPA. Компонент — это PHP-класс и Blade-шаблон; браузер отправляет запрос, сервер перерисовывает HTML, а библиотека точечно обновляет DOM. Ни API-слоя, ни дублирования валидации, ни отдельного фронтенд-проекта.
Как это работает
При первом рендере Livewire отдаёт HTML и снимок состояния компонента. Действие вроде wire:click отправляет POST на /livewire/update с этим снимком и именем метода. Сервер восстанавливает компонент, выполняет метод, рендерит шаблон заново и возвращает HTML вместе с новым снимком. Клиент применяет изменения через morph.
Отсюда два следствия. Состояние живёт на сервере, поэтому его не нужно синхронизировать вручную. И каждое действие — сетевой запрос, поэтому дорогие вычисления и N+1 в шаблоне бьют по отзывчивости сильнее, чем в SPA.
Установка
В Laravel 11 и новее Livewire ставится композером; сервис-провайдер регистрируется автоматически. Скрипты подключаются на страницу без ручной вставки тегов.
composer require livewire/livewire:^4.0
php artisan make:livewire Counter
php artisan route:list
php artisan about
Компонент появится в app/Livewire и resources/views/livewire. В четвёртой ветке по умолчанию доступны однофайловые компоненты, но классическая пара «класс + шаблон» полностью поддерживается и лучше подходит для постепенного внедрения.
Первый компонент
Публичные свойства — это состояние, которое видит браузер. Методы — действия. Шаблон возвращается из render.
<?php
namespace App\Livewire;
use Livewire\Component;
class Counter extends Component
{
public int $count = 0;
public function increment(): void
{
$this->count++;
}
public function render()
{
return view('livewire.counter');
}
}
<div>
<button wire:click="increment">Плюс</button>
<span>{{ $count }}</span>
</div>
Дальше компонент вставляется тегом livewire:counter или директивой @livewire. Не указывайте в публичных свойствах объекты, которые нельзя сериализовать: модели сохраняются по идентификатору, а произвольные классы — нет.
Формы и валидация
Валидация выполняется на сервере теми же правилами Laravel. Атрибуты #[Validate] убирают дублирование, а wire:model с модификаторами управляет моментом отправки.
use Livewire\Attributes\Validate;
class SubscribeForm extends Component
{
#[Validate('required|email|max:255')]
public string $email = '';
public function save(): void
{
$this->validate();
Subscriber::create(['email' => $this->email]);
session()->flash('ok', 'Готово');
$this->reset('email');
}
}
В шаблоне используйте wire:model.blur для проверки после ухода из поля и wire:model.live.debounce.400ms для живого поиска. Ошибки доступны через @error, а состояние загрузки — через wire:loading. Не отключайте серверную валидацию, даже если добавили клиентскую.
Списки и поиск
Пагинация и фильтры остаются на стороне сервера. Вычисляемые свойства кешируются в пределах запроса, поэтому их удобно использовать для тяжёлых выборок.
use Livewire\Attributes\Computed;
use Livewire\WithPagination;
class PostList extends Component
{
use WithPagination;
public string $q = '';
#[Computed]
public function posts()
{
return Post::query()
->when($this->q, fn ($query) => $query->where('title', 'like', "%{$this->q}%"))
->latest()
->paginate(15);
}
}
В шаблоне обращайтесь к $this->posts и не забудьте wire:key в цикле. Ключ должен быть стабильным и уникальным, иначе morph перепутает элементы и состояние полей поедет. Для больших списков включайте ленивую загрузку и не рендерите сотни строк сразу.
Связь компонентов и файлы
Дочерний компонент не должен знать о родителе. Вместо этого он отправляет событие через $this->dispatch(‘post-created’), а родитель подписывается атрибутом #[On(‘post-created’)] и обновляет список. Такой обмен работает и между разными частями страницы.
Загрузка файлов встроена: подключите трейт WithFileUploads, объявите публичное свойство с типом UploadedFile и сохраните файл через store. Временный файл живёт до сохранения, а валидация типа и размера выполняется теми же правилами, что и для обычных полей. Не храните бинарные данные в публичном свойстве — только ссылку.
Производительность
Главный враг — размер снимка и число запросов. Держите публичные свойства маленькими, не храните в них коллекции моделей и не вычисляйте агрегаты в шаблоне.
- wire:key на каждом элементе списка.
- #[Locked] для идентификаторов, которые пользователь не должен менять.
- wire:poll с разумным интервалом вместо постоянного опроса.
- Изолированные области и ленивые компоненты для тяжёлых блоков.
- Пагинация вместо вывода всей таблицы.
В четвёртой ветке появились изолированные области: тяжёлый блок обновляется отдельно и не блокирует остальную страницу. Там же автоматически проставляется атрибут состояния загрузки, поэтому вместо сложных конструкций с wire:target достаточно стилизовать элемент по data-loading.
Для отладки смотрите вкладку Network: видно размер снимка, время ответа и имя вызванного метода. Если ответ вырос до сотен килобайт, почти всегда причина — коллекция моделей в публичном свойстве.
Тестирование
Компоненты тестируются без браузера: Livewire поднимает их в тесте и позволяет вызывать методы, проверять свойства и HTML.
Livewire::test(Counter::class)
->assertSet('count', 0)
->call('increment')
->assertSet('count', 1)
->assertSee('1');
Для сценариев с формами проверяйте и успешную отправку, и ошибки валидации. Отдельный тест на авторизацию обязателен: метод компонента — это публичная точка входа, и проверка прав должна быть внутри, а не только в маршруте. Покрывайте границы: пустая строка, слишком длинное значение, чужой идентификатор. Это дешевле, чем ловить регрессии вручную в браузере.
Безопасность и типичные ошибки
Публичные свойства приходят от клиента и могут быть изменены. Никогда не доверяйте идентификатору из свойства, если он определяет доступ: загружайте запись с проверкой владельца. Для полей, которые нельзя менять, ставьте #[Locked].
Не выполняйте в действиях то, что должно происходить только по расписанию, и не передавайте в шаблон секреты. Файлы загружайте через встроенный механизм с валидацией типа и размера. Livewire не отменяет CSRF-защиту: токен подставляется автоматически, но кастомные fetch-запросы придётся оформлять вручную.
Итог
Livewire — самый короткий путь к интерактивному интерфейсу на Laravel без отдельного фронтенда: состояние на сервере, валидация в одном месте, обновление DOM по действию. Начните с одного компонента, измерьте число запросов и размер ответов, затем расширяйте.
Держите свойства компактными, ставьте wire:key, проверяйте права в действиях и используйте ленивую загрузку для тяжёлых блоков. Тогда серверный рендеринг остаётся простым и предсказуемым даже на нагруженном интерфейсе с десятками действий в минуту.