Livewire 3 — повний посібник
Livewire дозволяє будувати динамічні інтерфейси в Laravel на PHP і Blade — без окремого фронтенд-застосунку та без API-шару. Цей посібник веде від встановлення до продакшену: компоненти, стан, форми, події, завантаження файлів, тестування, безпека та продуктивність.
1. Що таке Livewire
Livewire — це повнофункціональний фреймворк для Laravel, який дозволяє будувати динамічні інтерфейси на PHP і Blade, не виходячи з бекенду. Ви не пишете окремий SPA, не піднімаєте REST/GraphQL-шар і не тримаєте другу модель даних у JavaScript.
Як це працює
Компонент Livewire — це PHP-клас і пов'язаний з ним Blade-шаблон. Під час першого завантаження сторінки компонент рендериться на сервері як звичайний HTML. Далі невеликий JS-рантайм Livewire перехоплює дії користувача (клік, введення, submit), надсилає AJAX-запит на сервер із поточним станом компонента, сервер заново рендерить компонент і повертає новий HTML. Рантайм порівнює старий і новий DOM та точково латає відмінності — сторінка не перезавантажується.
Браузер Сервер
│ │
│ клік по wire:click="save" │
├───────── POST /livewire/update ┤
│ { snapshot, calls, updates } │
│ ├── відновити компонент зі snapshot
│ ├── застосувати оновлення властивостей
│ ├── викликати метод save()
│ ├── виконати render()
│ { snapshot, html, effects } │
├◄───────────────────────────────┤
├── morph DOM (лише відмінності) │
▼ ▼Коли Livewire — правильний вибір
- Адмінки, CRM, дашборди, внутрішні панелі — багато форм і таблиць, мало складної анімації.
- Форми із залежними полями, багатокрокові візарди, жива валідація.
- Таблиці з пошуком, фільтрами, сортуванням і пагінацією.
- Команда сильна в PHP і не хоче утримувати окремий фронтенд-стек.
- Потрібен швидкий випуск функціональності без дублювання логіки між PHP і JS.
Коли краще взяти щось інше
- Інтерфейси з високою частотою оновлень: онлайн-редактори, канвас, drag-and-drop-конструктори, ігри.
- Offline-first застосунки та PWA зі складною локальною синхронізацією.
- Мобільні застосунки — там потрібен API, а не HTML-over-the-wire.
- Поганий або високолатентний канал: кожен цикл — це мережевий round-trip.
Версії
Цей посібник описує Livewire 3.x. Вимоги: PHP 8.1+, Laravel 10 і вище.
У третій версії Alpine.js входить у комплект, змінилися імена подій (dispatch замість
emit), з'явилися PHP-атрибути (#[Computed], #[Validate],
#[Url]), а wire:model за замовчуванням став відкладеним.
2. Встановлення та налаштування
Встановлення в наявний Laravel-проєкт займає одну команду.
composer require livewire/livewire
У Livewire 3 окремо підключати ассети не обов'язково: пакет сам вставляє
@livewireStyles і @livewireScripts у layout. Але якщо ви використовуєте
нестандартний layout або CSP, директиви можна прописати вручну.
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $title ?? 'Застосунок' }}</title>
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
@livewireStyles
</head>
<body class="antialiased">
{{ $slot }}
@livewireScripts
</body>
</html>@livewireScripts або він стоїть до підключення вашого власного
Alpine.js. Livewire 3 уже містить Alpine — другий екземпляр Alpine ламає реактивність.
Публікація конфігурації
php artisan livewire:publish --configОсновні параметри config/livewire.php:
| Параметр | Призначення |
|---|---|
class_namespace | Namespace класів компонентів. За замовчуванням App\Livewire. |
view_path | Каталог Blade-шаблонів компонентів. |
layout | Layout для full-page компонентів. |
temporary_file_upload | Диск, строк життя і правила для тимчасових завантажень. |
inject_assets | Автоматична вставка CSS/JS. Вимкніть, якщо ставите директиви вручну. |
navigate.show_progress_bar | Смуга прогресу для wire:navigate. |
Перевірка встановлення
php artisan livewire:make Counter
# CLASS: app/Livewire/Counter.php
# VIEW: resources/views/livewire/counter.blade.php3. Перший компонент
Компонент складається з двох файлів: класу та шаблону.
<?php
namespace App\Livewire;
use Livewire\Component;
class Counter extends Component
{
public int $count = 0;
public function increment(): void
{
$this->count++;
}
public function decrement(): void
{
$this->count--;
}
public function reset(): void
{
$this->count = 0;
}
public function render()
{
return view('livewire.counter');
}
}<div class="flex items-center gap-3">
<button type="button" wire:click="decrement" class="btn">−</button>
<span class="text-2xl font-bold">{{ $count }}</span>
<button type="button" wire:click="increment" class="btn">+</button>
<button type="button" wire:click="reset" class="btn btn-ghost">Скинути</button>
</div><div>.
Три способи вивести компонент
{{-- 1. Тег-синтаксис (рекомендовано) --}}
<livewire:counter />
{{-- 2. Директива --}}
@livewire('counter')
{{-- 3. З параметрами --}}
<livewire:counter :start="10" title="Лічильник замовлень" />Full-page компоненти
Компонент можна повісити прямо на маршрут — тоді він рендериться як самостійна сторінка.
use App\Livewire\Dashboard;
Route::get('/dashboard', Dashboard::class)->middleware('auth')->name('dashboard');use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
class Dashboard extends Component
{
#[Layout('layouts.app')]
#[Title('Панель керування')]
public function render()
{
return view('livewire.dashboard');
}
}Inline-компоненти
Для дуже маленьких компонентів шаблон можна повернути рядком — окремий Blade-файл не потрібен.
public function render()
{
return <<<'BLADE'
<div>
<button wire:click="$refresh">Оновити</button>
</div>
BLADE;
}4. Властивості компонента
Публічні властивості класу автоматично доступні в Blade-шаблоні та зберігаються між запитами. Саме вони утворюють стан компонента.
class UserProfile extends Component
{
public string $name = '';
public ?int $age = null;
public array $tags = [];
public bool $isPublic = false;
public function render()
{
return view('livewire.user-profile');
}
}Які типи можна зберігати
Між запитами стан серіалізується в JSON, тому набір допустимих типів обмежений.
| Можна | Не можна |
|---|---|
string, int, float, bool, null |
Замикання (Closure) |
array зі скалярних значень |
Ресурси (resource), потоки |
Eloquent-моделі та Collection моделей |
Довільні об'єкти без підтримки Wireable/Synth |
Carbon, DateTime, Stringable, enum |
Об'єкти з посиланнями на підключення, PDO тощо |
Ініціалізація: mount()
Метод mount() — конструктор компонента. Він виконується один раз, під час першого
рендеру, і отримує параметри, передані з Blade.
use App\Models\Order;
class OrderCard extends Component
{
public Order $order;
public string $mode;
public function mount(Order $order, string $mode = 'compact'): void
{
$this->order = $order;
$this->mode = $mode;
}
}<livewire:order-card :order="$order" mode="full" :key="$order->id" />Захист властивостей: #[Locked]
За замовчуванням клієнт може підмінити значення будь-якої публічної властивості. Для ідентифікаторів
і всього, що впливає на права доступу, це діра. Атрибут #[Locked] забороняє зміну
властивості з фронтенду.
use Livewire\Attributes\Locked;
class InvoiceEditor extends Component
{
#[Locked]
public int $invoiceId; // клієнт не зможе підставити чужий id
public string $comment = '';
}Приховані від JSON властивості
Властивості з модифікаторами protected і private не серіалізуються та
скидаються між запитами. Використовуйте їх лише для значень, які обчислюються
заново в кожному циклі.
class Report extends Component
{
public string $period = 'month';
// Скинеться після кожного запиту — не зберігайте тут стан.
protected array $cache = [];
}5. wire:model і прив'язка даних
wire:model пов'язує поле форми з властивістю компонента. У Livewire 3 прив'язка
за замовчуванням відкладена: значення їде на сервер не на кожне натискання клавіші,
а разом із наступною дією (клік, submit).
{{-- Відкладено: значення піде з наступним запитом --}}
<input type="text" wire:model="name">
{{-- Негайно: запит на кожну зміну --}}
<input type="text" wire:model.live="search">
{{-- Із затримкою 500 мс після завершення введення --}}
<input type="text" wire:model.live.debounce.500ms="search">
{{-- Не частіше разу на 2 с, поки користувач друкує --}}
<input type="text" wire:model.live.throttle.2s="search">
{{-- Лише при втраті фокусу --}}
<input type="text" wire:model.blur="email">
{{-- Властивість змінюється миттєво на клієнті, сервер дізнається пізніше --}}
<input type="text" wire:model.lazy="draft">Модифікатори
| Модифікатор | Поведінка | Коли застосовувати |
|---|---|---|
| — | Відкладене надсилання | Звичайні поля форми |
.live | Запит на кожну зміну | Живий пошук, залежні селекти |
.blur | Запит при втраті фокусу | Валідація поля на виході |
.debounce.Xms | Чекати паузу у введенні | Пошук під час набору |
.throttle.Xs | Не частіше одного разу за інтервал | Важкі запити |
.number | Приводити до числа | Числові поля |
.boolean | Приводити до булевого | Селекти «так/ні» |
.fill | Узяти початкове значення з HTML | Попередньо заповнені форми |
Усі типи полів
{{-- Текст, textarea --}}
<input type="text" wire:model="title">
<textarea wire:model="body"></textarea>
{{-- Один чекбокс: bool --}}
<input type="checkbox" wire:model="agreed">
{{-- Група чекбоксів: array --}}
<input type="checkbox" value="php" wire:model="skills">
<input type="checkbox" value="js" wire:model="skills">
<input type="checkbox" value="sql" wire:model="skills">
{{-- Радіокнопки --}}
<input type="radio" value="card" wire:model="payment">
<input type="radio" value="cash" wire:model="payment">
{{-- Select --}}
<select wire:model.live="categoryId">
<option value="">Усі категорії</option>
@foreach ($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
@endforeach
</select>
{{-- Множинний select --}}
<select wire:model="tagIds" multiple>
@foreach ($tags as $tag)
<option value="{{ $tag->id }}">{{ $tag->name }}</option>
@endforeach
</select>Вкладені дані
Крапкова нотація працює і для масивів, і для властивостей моделей.
public array $form = [
'name' => '',
'address' => ['city' => '', 'street' => ''],
];
public Post $post;<input wire:model="form.name">
<input wire:model="form.address.city">
<input wire:model="post.title">wire:model="post.title" працювало, поле має
бути дозволене правилом валідації (rules або #[Validate]) — інакше Livewire
викине виняток. Це захист від масового присвоєння.
6. Дії (actions)
Дія — публічний метод компонента, який викликається з шаблону. Це заміна звичному
fetch() + обробник на сервері.
{{-- Клік --}}
<button wire:click="save">Зберегти</button>
{{-- Надсилання форми (submit перехоплюється) --}}
<form wire:submit="save">
<input wire:model="title">
<button type="submit">Надіслати</button>
</form>
{{-- Клавіші --}}
<input wire:keydown.enter="search" wire:keydown.escape="clear">
{{-- Інші події DOM --}}
<div wire:mouseenter="preload">…</div>
<select wire:change="applyFilter">…</select>Параметри
<button wire:click="delete({{ $post->id }})">Видалити</button>
<button wire:click="setStatus('published')">Опублікувати</button>
<button wire:click="move({{ $item->id }}, 'up')">Вгору</button>public function delete(int $postId): void
{
$post = Post::findOrFail($postId);
$this->authorize('delete', $post); // авторизацію перевіряємо завжди
$post->delete();
$this->dispatch('notify', message: 'Допис видалено');
}delete(999) з довільним id. Перевірка прав усередині
методу обов'язкова.
Model binding у параметрах
Livewire вміє розв'язувати моделі за id, як це робить роутер Laravel.
public function archive(Post $post): void
{
$this->authorize('update', $post);
$post->update(['archived_at' => now()]);
}Модифікатори дій
{{-- Підтвердження у браузері до надсилання --}}
<button wire:click="delete" wire:confirm="Точно видалити? Скасувати буде неможливо.">Видалити</button>
{{-- preventDefault / stopPropagation --}}
<a href="#" wire:click.prevent="open">Відкрити</a>
<div wire:click.stop="select">…</div>
{{-- Спрацює один раз --}}
<button wire:click.once="init">Ініціалізувати</button>
{{-- Лише якщо клікнули саме по цьому елементу --}}
<div wire:click.self="close">…</div>Магічні дії
| Дія | Що робить |
|---|---|
$refresh | Перерендерити компонент без зміни стану |
$set('prop', value) | Присвоїти властивості значення |
$toggle('prop') | Інвертувати булеву властивість |
$dispatch('event') | Надіслати подію Livewire |
$parent.method() | Викликати метод батьківського компонента |
<button wire:click="$refresh">Оновити</button>
<button wire:click="$set('tab', 'settings')">Налаштування</button>
<button wire:click="$toggle('showFilters')">Фільтри</button>
<button wire:click="$parent.closeModal()">Закрити</button>Редиректи
public function store(): void
{
$this->validate();
$post = Post::create($this->only('title', 'body'));
session()->flash('status', 'Допис створено');
$this->redirect(route('posts.show', $post), navigate: true);
}7. Життєвий цикл запиту
Розуміння порядку виконання знімає 90% «містичних» багів. Розрізняють первинний рендер (звичайний HTTP-запит сторінки) та подальші оновлення (AJAX-запити Livewire).
Первинний рендер
1. Створюється екземпляр компонента
2. boot()
3. mount($params)
4. booted()
5. hydrate-хуки НЕ викликаються
6. render()
7. HTML вставляється у сторінкуПодальше оновлення
1. Надходить POST /livewire/update зі snapshot стану
2. Перевіряється контрольна сума snapshot (захист від підміни)
3. Створюється екземпляр компонента
4. boot()
5. Стан відновлюється зі snapshot
6. hydrate() і hydrateFoo() для кожної властивості
7. booted()
8. updating($prop, $value) / updatingFoo($value) — до присвоєння
9. Властивості отримують нові значення
10. updated($prop, $value) / updatedFoo($value) — після присвоєння
11. Викликаються методи з черги calls (дії)
12. rendering()
13. render()
14. rendered($view, $html)
15. dehydrate() — стан пакується назад у snapshot
16. Відповідь: новий HTML + effects (події, редиректи, dispatch у браузер)Snapshot і контрольна сума
Увесь стан компонента їде на клієнт і назад в атрибуті wire:snapshot. Щоб клієнт не
підмінив дані, Livewire підписує snapshot HMAC-підписом на основі APP_KEY. За розбіжності
підпису запит відхиляється з помилкою «Livewire encountered corrupt data».
render() або через
#[Computed].
Що відбувається з DOM
Livewire не замінює вузол цілком, а виконує morph: обходить старе й нове дерево і змінює
лише відмінності. Тому фокус у полі введення, позиція скролу і стан Alpine зберігаються. Якщо
структура списку змінюється, morph-алгоритму потрібні підказки — див. wire:key у розділі 10.
8. Хуки життєвого циклу
Хуки дозволяють втрутитися в будь-яку фазу циклу.
class ProductEditor extends Component
{
public Product $product;
public string $name = '';
public float $price = 0;
// Виконується на початку КОЖНОГО запиту, до відновлення стану.
public function boot(): void
{
// Гарне місце для залежностей, які не можна серіалізувати.
}
// Лише під час першого рендеру.
public function mount(Product $product): void
{
$this->product = $product;
$this->name = $product->name;
$this->price = $product->price;
}
// Після відновлення стану, у кожному подальшому запиті.
public function hydrate(): void
{
}
// Після boot() і відновлення стану.
public function booted(): void
{
}
// Перед зміною будь-якої властивості.
public function updating(string $property, mixed $value): void
{
}
// Після зміни будь-якої властивості.
public function updated(string $property, mixed $value): void
{
$this->validateOnly($property);
}
// Лише для властивості $price.
public function updatedPrice(mixed $value): void
{
$this->price = round((float) $value, 2);
}
// Для вкладеного ключа form.email
public function updatedFormEmail(mixed $value): void
{
$this->form['email'] = strtolower(trim($value));
}
public function rendering(): void
{
}
public function rendered(mixed $view, string $html): void
{
}
// Перед пакуванням стану у snapshot.
public function dehydrate(): void
{
}
public function render()
{
return view('livewire.product-editor');
}
}Правила іменування
| Властивість | Хук |
|---|---|
$price | updatedPrice() |
$isActive | updatedIsActive() |
$form['email'] | updatedFormEmail() |
$post->title | updatedPostTitle() |
public function updatedSearch() { $this->resetPage(); }
9. Валідація та Form-об'єкти
Livewire використовує валідатор Laravel — правила, повідомлення й локалізація ті самі.
Спосіб 1: атрибути (Livewire 3)
use Livewire\Attributes\Validate;
class ContactForm extends Component
{
#[Validate('required|string|min:2|max:80')]
public string $name = '';
#[Validate('required|email:rfc,dns')]
public string $email = '';
#[Validate('required|string|min:20|max:2000')]
public string $message = '';
#[Validate('accepted', message: 'Потрібна згода на обробку даних.')]
public bool $consent = false;
public function submit(): void
{
$data = $this->validate();
Contact::create($data);
$this->reset();
session()->flash('status', 'Повідомлення надіслано');
}
}Спосіб 2: метод rules()
Потрібен, коли правила залежать від стану.
protected function rules(): array
{
return [
'email' => [
'required',
'email',
Rule::unique('users', 'email')->ignore($this->userId),
],
'password' => $this->userId ? 'nullable|min:8|confirmed' : 'required|min:8|confirmed',
];
}
protected function messages(): array
{
return [
'email.unique' => 'Така адреса вже зареєстрована.',
];
}
protected function validationAttributes(): array
{
return [
'email' => 'адреса електронної пошти',
];
}Жива валідація
public function updated(string $property): void
{
$this->validateOnly($property); // перевіряємо лише змінене поле
}<form wire:submit="submit" novalidate>
<label for="email">E-mail</label>
<input id="email" type="email" wire:model.blur="email"
class="@error('email') border-red-500 @enderror">
@error('email')
<p class="text-sm text-red-600">{{ $message }}</p>
@enderror
<button type="submit" wire:loading.attr="disabled">
<span wire:loading.remove wire:target="submit">Надіслати</span>
<span wire:loading wire:target="submit">Надсилаємо…</span>
</button>
</form>Ручне керування помилками
$this->addError('email', 'Домен у чорному списку.');
$this->resetValidation('email');
$this->resetValidation(); // скинути всі
$this->validateOnly('email');
// Проброс помилки як у звичайному Laravel-контролері
throw ValidationException::withMessages([
'code' => 'Невірний код підтвердження.',
]);Form-об'єкти
Коли форма розростається, виносьте її в окремий клас. Компонент лишається тонким, а правила й дані форми можна перевикористати між «створити» та «редагувати».
php artisan livewire:form PostForm<?php
namespace App\Livewire\Forms;
use App\Models\Post;
use Livewire\Attributes\Validate;
use Livewire\Form;
class PostForm extends Form
{
public ?Post $post = null;
#[Validate('required|string|min:5|max:180')]
public string $title = '';
#[Validate('required|string|min:50')]
public string $body = '';
#[Validate('nullable|date|after_or_equal:today')]
public ?string $publishAt = null;
public function setPost(Post $post): void
{
$this->post = $post;
$this->title = $post->title;
$this->body = $post->body;
$this->publishAt = $post->publish_at?->toDateString();
}
public function store(): Post
{
$this->validate();
return Post::create($this->except('post'));
}
public function update(): void
{
$this->validate();
$this->post->update($this->except('post'));
}
}class PostEditor extends Component
{
public PostForm $form;
public function mount(?Post $post = null): void
{
if ($post?->exists) {
$this->form->setPost($post);
}
}
public function save(): void
{
$this->form->post
? $this->form->update()
: $this->form->store();
$this->redirect(route('posts.index'), navigate: true);
}
}<form wire:submit="save">
<input wire:model="form.title">
@error('form.title') <span>{{ $message }}</span> @enderror
<textarea wire:model="form.body"></textarea>
@error('form.body') <span>{{ $message }}</span> @enderror
<button type="submit">Зберегти</button>
</form>10. Вкладені компоненти та wire:key
Компоненти вкладаються один в одного. Кожен вкладений компонент — незалежна одиниця зі своїм станом і своїм циклом оновлення: клік усередині дочірнього компонента не перерендерює батьківський.
<div>
<h1>Замовлення</h1>
@foreach ($orders as $order)
<livewire:order-row :order="$order" :key="'order-'.$order->id" />
@endforeach
</div>:key обов'язковий у циклах. Без унікального ключа morph-алгоритм
переплутає елементи під час сортування, фільтрації чи видалення: значення полів «переїдуть» на сусідні
рядки. Ключ має бути стабільним — $loop->index не підходить, використовуйте id запису.
Передача даних униз
Параметри передаються один раз, у mount(). Під час подальших рендерів батьківського
компонента дочірній не оновлюється автоматично — він живе своїм життям.
use Livewire\Attributes\Reactive;
class OrderTotal extends Component
{
// З #[Reactive] значення приїжджатиме від батька під час кожного його рендеру.
#[Reactive]
public int $quantity;
}Звернення до батька
<button wire:click="$parent.refreshList()">Оновити список</button>Двобічний зв'язок: #[Modelable]
Дозволяє використовувати wire:model на самому компоненті — зручно для полів-віджетів.
use Livewire\Attributes\Modelable;
class RatingInput extends Component
{
#[Modelable]
public int $value = 0;
public function set(int $value): void
{
$this->value = $value;
}
}{{-- У батьківському шаблоні --}}
<livewire:rating-input wire:model.live="review.rating" />Умовний рендер вкладених компонентів
@if ($showDetails)
<livewire:order-details :order-id="$orderId" :key="'details-'.$orderId" />
@endif11. Події
Події — спосіб зв'язати компоненти, які не перебувають у відносинах «батько — нащадок».
У Livewire 3 надсилання — це dispatch() (у версії 2 було emit()).
Надсилання
// Проста подія
$this->dispatch('post-created');
// З іменованими параметрами
$this->dispatch('post-created', postId: $post->id, title: $post->title);
// Лише конкретному компоненту
$this->dispatch('refresh')->to(OrderList::class);
// Лише самому собі
$this->dispatch('recalculate')->self();{{-- Прямо з шаблону --}}
<button wire:click="$dispatch('open-modal', { name: 'create-order' })">Нове замовлення</button>Приймання
use Livewire\Attributes\On;
class OrderList extends Component
{
public array $orders = [];
#[On('post-created')]
public function onPostCreated(int $postId, string $title): void
{
$this->orders[] = ['id' => $postId, 'title' => $title];
}
// Динамічне ім'я події
#[On('order-updated.{orderId}')]
public function onOrderUpdated(): void
{
$this->refreshList();
}
}Альтернатива — масив $listeners (сумісно з версією 2):
protected $listeners = [
'post-created' => 'onPostCreated',
'refresh' => '$refresh',
];Події у браузер
Livewire може надіслати звичайну DOM-подію — її зловить Alpine.js або ваш власний JS. Це правильний спосіб показувати тости й відкривати модальні вікна.
$this->dispatch('notify', type: 'success', message: 'Замовлення збережено');<div x-data="{ show: false, message: '' }"
x-on:notify.window="message = $event.detail.message; show = true; setTimeout(() => show = false, 3000)"
x-show="show"
x-transition
class="toast">
<span x-text="message"></span>
</div>document.addEventListener('notify', (event) => {
console.log(event.detail.message);
});12. Обчислювані властивості
#[Computed] — спосіб отримувати похідні дані без зберігання їх у стані. Результат
кешується на час одного запиту, тож звернення до нього тричі в шаблоні не дасть трьох SQL-запитів.
use Livewire\Attributes\Computed;
use Illuminate\Support\Collection;
class Cart extends Component
{
public array $items = [];
#[Computed]
public function products(): Collection
{
return Product::whereIn('id', array_keys($this->items))->get();
}
#[Computed]
public function subtotal(): float
{
return $this->products->sum(
fn (Product $product) => $product->price * $this->items[$product->id]
);
}
#[Computed]
public function total(): float
{
return round($this->subtotal * 1.2, 2); // з ПДВ
}
}<div>
@foreach ($this->products as $product)
<div>{{ $product->name }} — {{ $this->items[$product->id] }} шт.</div>
@endforeach
<p>Сума: {{ number_format($this->subtotal, 2, ',', ' ') }} €</p>
<p>Разом із ПДВ: {{ number_format($this->total, 2, ',', ' ') }} €</p>
</div>$this. У шаблоні обчислювана властивість доступна як
$this->products, а не $products. Це відрізняє її від звичайних публічних
властивостей.
Кешування між запитами
// Кеш на 5 хвилин у загальному кеші застосунку
#[Computed(persist: true, seconds: 300)]
public function statistics(): array
{
return [
'orders' => Order::whereMonth('created_at', now()->month)->count(),
'revenue' => Order::whereMonth('created_at', now()->month)->sum('total'),
];
}
// Спільний кеш для всіх користувачів
#[Computed(cache: true, key: 'global-stats')]
public function globalStats(): array
{
return app(StatsService::class)->build();
}Скидання кешу
public function addItem(int $productId): void
{
$this->items[$productId] = ($this->items[$productId] ?? 0) + 1;
unset($this->products, $this->subtotal, $this->total); // скинути кеш
}Дані в render() vs #[Computed]
Обидва варіанти не зберігають дані у стані. Різниця в області видимості: render() віддає
змінні лише шаблону, а #[Computed] доступне і в PHP-методах, і в шаблоні, та кешується.
public function render()
{
return view('livewire.orders', [
'orders' => Order::query()
->when($this->search, fn ($q) => $q->where('number', 'like', "%{$this->search}%"))
->latest()
->paginate(20),
]);
}13. Стан в URL і сесії
Фільтри, пошук і активна вкладка мають переживати перезавантаження сторінки та потрапляти в посилання,
яким можна поділитися. Атрибут #[Url] синхронізує властивість із рядком запиту.
use Livewire\Attributes\Url;
class ProductCatalog extends Component
{
#[Url]
public string $search = '';
// Інше ім'я параметра в URL
#[Url(as: 'cat')]
public ?int $categoryId = null;
// Не показувати в URL, поки значення збігається з початковим
#[Url(except: '')]
public string $sort = 'popular';
// Зберігати між переходами wire:navigate
#[Url(keep: true)]
public int $perPage = 24;
// Використовувати history.pushState замість replaceState
#[Url(history: true)]
public string $tab = 'all';
}Результат: /catalog?search=ноутбук&cat=5&sort=price-asc&tab=sale
Зберігання в сесії
use Livewire\Attributes\Session;
class Sidebar extends Component
{
#[Session]
public bool $collapsed = false;
#[Session(key: 'admin.table.density')]
public string $density = 'comfortable';
}Ручна робота із сесією та flash
public function save(): void
{
$this->validate();
Setting::updateOrCreate(['key' => 'theme'], ['value' => $this->theme]);
session()->flash('status', 'Налаштування збережено');
}@if (session('status'))
<div class="docs-note docs-note--tip">{{ session('status') }}</div>
@endif#[Url]-властивість при зміні смикає
History API. Для полів, що змінюються на кожне натискання клавіші, комбінуйте з
wire:model.live.debounce.
14. Завантаження файлів
Трейт WithFileUploads додає повноцінне завантаження: файл іде на сервер одразу після
вибору, потрапляє у тимчасове сховище й стає доступним як об'єкт
TemporaryUploadedFile.
use Livewire\WithFileUploads;
use Livewire\Attributes\Validate;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
class AvatarUploader extends Component
{
use WithFileUploads;
#[Validate('required|image|mimes:jpg,jpeg,png,webp|max:4096')] // до 4 МБ
public $avatar;
#[Validate(['documents.*' => 'file|mimes:pdf,docx|max:10240'])]
public array $documents = [];
public function save(): void
{
$this->validate();
$path = $this->avatar->store('avatars', 'public');
auth()->user()->update(['avatar_path' => $path]);
$this->reset('avatar');
$this->dispatch('notify', message: 'Аватар оновлено');
}
public function removeDocument(int $index): void
{
unset($this->documents[$index]);
$this->documents = array_values($this->documents);
}
}<form wire:submit="save">
<input type="file" wire:model="avatar" accept="image/*">
{{-- Прогрес завантаження --}}
<div wire:loading wire:target="avatar" class="text-sm">Завантажуємо файл…</div>
{{-- Прев'ю до збереження --}}
@if ($avatar)
<img src="{{ $avatar->temporaryUrl() }}" alt="Прев'ю" class="w-32 h-32 object-cover rounded">
@endif
@error('avatar') <p class="text-red-600">{{ $message }}</p> @enderror
<button type="submit" wire:loading.attr="disabled">Зберегти</button>
</form>Точний індикатор прогресу
<div x-data="{ progress: 0, uploading: false }"
x-on:livewire-upload-start="uploading = true"
x-on:livewire-upload-finish="uploading = false; progress = 0"
x-on:livewire-upload-error="uploading = false"
x-on:livewire-upload-progress="progress = $event.detail.progress">
<input type="file" wire:model="avatar">
<div x-show="uploading" class="h-2 bg-gray-200 rounded">
<div class="h-2 bg-green-600 rounded" :style="`width: ${progress}%`"></div>
</div>
</div>Налаштування тимчасового сховища
'temporary_file_upload' => [
'disk' => 's3', // або null → диск за замовчуванням
'rules' => ['file', 'max:12288'],
'directory' => 'livewire-tmp',
'middleware' => 'throttle:60,1',
'preview_mimes' => ['png', 'jpeg', 'jpg', 'webp', 'gif', 'mp4', 'pdf'],
'max_upload_time' => 5, // хвилин до автоочищення
],upload_max_filesize, post_max_size і
max_execution_time в php.ini, а також client_max_body_size у
nginx мають бути не меншими за ваш max: у правилах. Інакше завантаження обривається без
зрозумілої помилки.
15. Пагінація
Трейт WithPagination підключає пагінацію Laravel без перезавантаження сторінки.
use Livewire\WithPagination;
use Livewire\Attributes\Url;
class OrderTable extends Component
{
use WithPagination;
#[Url]
public string $search = '';
#[Url]
public string $status = '';
public string $sortField = 'created_at';
public string $sortDirection = 'desc';
public int $perPage = 25;
// Скидаємо на першу сторінку при зміні фільтрів.
public function updatedSearch(): void
{
$this->resetPage();
}
public function updatedStatus(): void
{
$this->resetPage();
}
public function sortBy(string $field): void
{
if ($this->sortField === $field) {
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
} else {
$this->sortField = $field;
$this->sortDirection = 'asc';
}
$this->resetPage();
}
public function render()
{
return view('livewire.order-table', [
'orders' => Order::query()
->with('customer')
->when($this->search, fn ($q) => $q->where('number', 'like', "%{$this->search}%"))
->when($this->status, fn ($q) => $q->where('status', $this->status))
->orderBy($this->sortField, $this->sortDirection)
->paginate($this->perPage),
]);
}
}<div>
<input type="search" wire:model.live.debounce.400ms="search" placeholder="Пошук за номером">
<table>
<thead>
<tr>
<th wire:click="sortBy('number')" style="cursor:pointer">Номер</th>
<th wire:click="sortBy('created_at')" style="cursor:pointer">Дата</th>
<th wire:click="sortBy('total')" style="cursor:pointer">Сума</th>
</tr>
</thead>
<tbody>
@forelse ($orders as $order)
<tr wire:key="order-{{ $order->id }}">
<td>{{ $order->number }}</td>
<td>{{ $order->created_at->format('d.m.Y') }}</td>
<td>{{ number_format($order->total, 2, ',', ' ') }}</td>
</tr>
@empty
<tr><td colspan="3">Замовлень не знайдено</td></tr>
@endforelse
</tbody>
</table>
{{ $orders->links() }}
</div>Корисні деталі
// Власне ім'я параметра сторінки — потрібне, якщо на сторінці два пагінатори
protected string $paginationTheme = 'tailwind'; // або 'bootstrap'
public function render()
{
return view('livewire.dashboard', [
'orders' => Order::paginate(10, pageName: 'orders-page'),
'invoices' => Invoice::paginate(10, pageName: 'invoices-page'),
]);
}paginate() виконує додатковий
COUNT(*). Якщо загальна кількість сторінок не потрібна, використовуйте
simplePaginate() — він помітно дешевший.
16. Стани завантаження
Кожна дія — це мережевий запит. Без індикації інтерфейс здається завислим. Livewire дає декларативні директиви, які не потребують жодного рядка JS.
{{-- Показати під час будь-якого запиту компонента --}}
<div wire:loading>Завантаження…</div>
{{-- Приховати під час запиту --}}
<div wire:loading.remove>Вміст</div>
{{-- Лише для конкретної дії --}}
<button wire:click="save">Зберегти</button>
<span wire:loading wire:target="save">Зберігаємо…</span>
{{-- Для кількох цілей --}}
<span wire:loading wire:target="save,delete,publish">Обробляємо…</span>
{{-- Для конкретної властивості --}}
<span wire:loading wire:target="search">Шукаємо…</span>
{{-- Виключити ціль --}}
<div wire:loading wire:target.except="search">Оновлюємо…</div>Модифікатори
{{-- Заблокувати кнопку --}}
<button wire:click="save" wire:loading.attr="disabled">Зберегти</button>
{{-- Додати CSS-клас --}}
<button wire:click="save" wire:loading.class="opacity-50 cursor-wait">Зберегти</button>
{{-- Прибрати клас --}}
<div wire:loading.class.remove="bg-white">…</div>
{{-- Затримка: показати індикатор, лише якщо запит довший за 300 мс --}}
<div wire:loading.delay>Завантаження…</div>
{{-- Точні пороги: shortest 50ms, shorter 100ms, short 150ms,
default 200ms, long 300ms, longer 500ms, longest 1s --}}
<div wire:loading.delay.long>Завантаження…</div>
{{-- Керування відображенням --}}
<div wire:loading.flex>…</div>
<div wire:loading.grid>…</div>
<div wire:loading.inline-flex>…</div>Незбережені зміни: wire:dirty
<input wire:model="title">
<span wire:dirty wire:target="title" class="text-amber-600">Є незбережені зміни</span>
<button wire:click="save" wire:dirty.class="ring-2 ring-amber-400">Зберегти</button>Втрата з'єднання: wire:offline
<div wire:offline class="banner banner--warn">
Немає з'єднання із сервером. Зміни не зберігаються.
</div>Скелетон під час першого завантаження
<div wire:init="loadHeavyData">
@if ($loaded)
{{-- реальні дані --}}
@else
<div class="skeleton h-40 w-full animate-pulse bg-gray-200"></div>
@endif
</div>18. Ліниве завантаження та polling
Ліниве завантаження компонента
Важкий компонент можна не рендерити в першій відповіді: сторінка віддається миттєво із заглушкою, а вміст підвантажується другим запитом.
<livewire:revenue-chart lazy />use Livewire\Attributes\Lazy;
#[Lazy]
class RevenueChart extends Component
{
public function placeholder(): string
{
return <<<'BLADE'
<div class="skeleton h-64 w-full animate-pulse rounded bg-gray-200"></div>
BLADE;
}
public function render()
{
return view('livewire.revenue-chart', [
'points' => app(RevenueService::class)->monthly(), // важкий запит
]);
}
}
За замовчуванням лінивий компонент вантажиться одразу після відмальовування сторінки. Варіант
#[Lazy(isolate: false)] об'єднує запити кількох лінивих компонентів в один, а
lazy="on-load" / lazy="on-scroll" керують моментом завантаження.
{{-- Підвантажити, коли блок потрапить у в'юпорт --}}
<livewire:revenue-chart lazy="on-scroll" />Відкладена ініціалізація: wire:init
<div wire:init="loadStats">
@if ($stats)
…
@else
<div class="skeleton"></div>
@endif
</div>Опитування сервера: wire:poll
{{-- Оновлювати кожні 2 с (значення за замовчуванням — 2500 мс) --}}
<div wire:poll>…</div>
{{-- Власний інтервал --}}
<div wire:poll.5s>…</div>
<div wire:poll.750ms>…</div>
{{-- Викликати конкретний метод --}}
<div wire:poll.10s="refreshQueue">…</div>
{{-- Зупиняти опитування, коли вкладка неактивна --}}
<div wire:poll.visible.5s="refreshQueue">…</div>
{{-- Зупиняти через 5 хвилин неактивності користувача --}}
<div wire:poll.keep-alive.5s>…</div>wire:poll.2s на сторінці, відкритій у 100
співробітників, — це 3000 запитів за хвилину до вашого PHP-процесу. Завжди додавайте
.visible, збільшуйте інтервал до розумного і розглядайте websockets (Laravel Echo +
Reverb) для по-справжньому живих даних.
19. Alpine.js, $wire і JS-хуки
Alpine.js входить у постачання Livewire 3. Усередині компонента доступний об'єкт $wire —
проксі до стану й методів PHP-компонента прямо з JavaScript.
<div x-data="{ open: false }">
{{-- Суто клієнтський стан: сервер не бере участі --}}
<button x-on:click="open = !open">Деталі</button>
<div x-show="open" x-transition>
{{-- Читання властивості компонента --}}
<p x-text="$wire.title"></p>
{{-- Запис властивості --}}
<button x-on:click="$wire.title = 'Новий заголовок'">Перейменувати</button>
{{-- Виклик методу (повертає Promise) --}}
<button x-on:click="$wire.save()">Зберегти</button>
{{-- Метод з очікуванням результату --}}
<button x-on:click="await $wire.calculate(); open = false">Розрахувати</button>
{{-- Виклик без перемальовування --}}
<button x-on:click="$wire.$set('tab', 'stats', false)">Статистика</button>
</div>
</div>Двобічний зв'язок: $wire.entangle
<div x-data="{ query: $wire.entangle('search') }">
<input x-model="query">
<p x-show="query.length > 0">Шукаємо: <span x-text="query"></span></p>
</div>
{{-- Відкладена синхронізація: не ганяти запит на кожен символ --}}
<div x-data="{ query: $wire.entangle('search').live }">…</div>Власні скрипти всередині компонента
@script
<script>
// Виконається один раз під час ініціалізації компонента.
const chart = new Chart(document.getElementById('sales'), {
type: 'line',
data: @json($chartData),
});
// $wire доступний і тут.
$wire.on('data-updated', ({ points }) => {
chart.data.datasets[0].data = points;
chart.update();
});
</script>
@endscript{{-- Завантажити зовнішню бібліотеку один раз на всю сторінку --}}
@assets
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
@endassetsІгнорування піддерева
Якщо сторонній віджет сам керує DOM, morph-алгоритм йому лише заважає.
<div wire:ignore>
<select id="select2-field">…</select>
</div>
{{-- Ігнорувати лише дітей, але оновлювати атрибути самого елемента --}}
<div wire:ignore.self>…</div>Глобальні JS-хуки
document.addEventListener('livewire:init', () => {
// Перед надсиланням кожного запиту
Livewire.hook('request', ({ uri, options, payload, respond, succeed, fail }) => {
options.headers['X-Tenant'] = window.tenantId;
succeed(({ status, json }) => {
console.debug('Livewire відповів', status);
});
fail(({ status, preventDefault }) => {
if (status === 419) {
preventDefault();
window.location.reload(); // сплив CSRF-токен
}
});
});
// Перед і після morph окремого елемента
Livewire.hook('morph.updated', ({ el, component }) => {});
// Компонент ініціалізовано
Livewire.hook('component.init', ({ component }) => {});
});
// Програмний доступ до компонентів
Livewire.dispatch('refresh-orders');
Livewire.find('component-id').call('save');
Livewire.all().forEach((component) => component.$refresh());20. Тестування
Livewire дає власний тестовий API поверх Laravel. Тести швидкі — браузер не потрібен, працює звичайний PHPUnit або Pest.
<?php
use App\Livewire\Counter;
use Livewire\Livewire;
it('збільшує лічильник', function () {
Livewire::test(Counter::class)
->assertSet('count', 0)
->call('increment')
->assertSet('count', 1)
->call('increment')
->assertSet('count', 2)
->call('decrement')
->assertSet('count', 1);
});use App\Livewire\ContactForm;
use App\Models\Contact;
use Livewire\Livewire;
it('валідує обов\'язкові поля', function () {
Livewire::test(ContactForm::class)
->set('name', '')
->set('email', 'не-email')
->call('submit')
->assertHasErrors([
'name' => 'required',
'email' => 'email',
])
->assertNoRedirect();
});
it('зберігає коректну заявку', function () {
Livewire::test(ContactForm::class)
->set('name', 'Ірина Ковач')
->set('email', 'irina@example.com')
->set('message', str_repeat('Потрібен сайт для клініки. ', 3))
->set('consent', true)
->call('submit')
->assertHasNoErrors()
->assertDispatched('notify');
expect(Contact::where('email', 'irina@example.com')->exists())->toBeTrue();
});Основні твердження
| Метод | Перевіряє |
|---|---|
assertSet('prop', $value) | Значення властивості |
assertNotSet('prop', $value) | Значення відрізняється |
assertSee('текст') | Текст присутній у HTML |
assertDontSee('текст') | Тексту немає |
assertSeeHtml('<b>') | Розмітка присутня |
assertHasErrors(['email']) | Помилки валідації |
assertHasNoErrors() | Помилок немає |
assertDispatched('event') | Подію надіслано |
assertRedirect(route(…)) | Виконано редирект |
assertStatus(403) | HTTP-статус |
assertForbidden() | Доступ заборонено |
assertCount('items', 3) | Розмір масиву/колекції |
Автентифікація, параметри, події
// Від імені користувача
Livewire::actingAs($admin)
->test(OrderTable::class)
->assertSee('Усі замовлення');
// З параметрами mount()
Livewire::test(OrderCard::class, ['order' => $order, 'mode' => 'full'])
->assertSee($order->number);
// Приймання події
Livewire::test(OrderList::class)
->dispatch('order-created', orderId: 42)
->assertSee('Замовлення №42');
// Завантаження файлу
use Illuminate\Http\UploadedFile;
Livewire::test(AvatarUploader::class)
->set('avatar', UploadedFile::fake()->image('avatar.jpg', 400, 400))
->call('save')
->assertHasNoErrors();
// Компонент усередині сторінки
$this->get('/dashboard')
->assertSeeLivewire(Dashboard::class)
->assertDontSeeLivewire(AdminPanel::class);21. Безпека
1. Авторизація всередині кожної дії
public function delete(int $postId): void
{
$post = Post::findOrFail($postId);
$this->authorize('delete', $post); // Policy — обов'язково
$post->delete();
}// Авторизація всього компонента
public function mount(Project $project): void
{
$this->authorize('view', $project);
$this->project = $project;
}2. #[Locked] для ідентифікаторів
use Livewire\Attributes\Locked;
class InvoiceEditor extends Component
{
#[Locked]
public int $invoiceId; // підмінити з фронтенду не можна
public string $note = '';
}Без #[Locked] достатньо одного рядка в консолі браузера:
Livewire.find('...').set('invoiceId', 999) // чужий рахунок3. Правила для прив'язки до моделей
wire:model="post.title" працює, лише якщо поле дозволене правилом валідації —
так Livewire захищає від масового присвоєння.
protected function rules(): array
{
return [
'post.title' => 'required|string|max:180',
'post.body' => 'required|string',
// 'post.user_id' свідомо НЕ включаємо — інакше допис можна переписати на іншого автора
];
}4. Екранування виводу
{{-- Безпечно: екранується --}}
{{ $comment->body }}
{{-- Небезпечно: сирий HTML від користувача --}}
{!! $comment->body !!}
{{-- Якщо HTML потрібен — санітизуйте на сервері --}}
{!! clean($comment->body) !!}5. Обмеження частоти
use Illuminate\Support\Facades\RateLimiter;
public function login(): void
{
$key = 'login:' . request()->ip();
if (RateLimiter::tooManyAttempts($key, 5)) {
throw ValidationException::withMessages([
'email' => 'Забагато спроб. Повторіть за хвилину.',
]);
}
RateLimiter::hit($key, 60);
// …
}6. Не зберігайте секрети у властивостях
class PaymentForm extends Component
{
public string $cardLast4 = ''; // ок
// НЕ МОЖНА: поїде в браузер у відкритому вигляді всередині wire:snapshot
// public string $apiSecret = '';
// public string $fullCardNumber = '';
protected function gateway(): PaymentGateway
{
return app(PaymentGateway::class); // секрети лишаються на сервері
}
}Чек-лист перед релізом
$this->authorize()є в кожній дії, що змінює дані.- Усі ідентифікатори позначені
#[Locked]. - Правила валідації не містять службових полів (
user_id,role,price). - Публічні властивості не містять токенів, ключів і персональних даних понад потрібне.
- Завантаження файлів обмежене за MIME-типами й розміром.
- Форми входу та надсилання повідомлень захищені rate limiting.
{!! !!}використовується лише для довіреного або санітизованого HTML.
22. Продуктивність
1. Мінімум стану в публічних властивостях
public Collection $products; // 500 моделей їздять на клієнт і назад
public function mount(): void
{
$this->products = Product::with('category', 'images')->get();
}public function render()
{
return view('livewire.catalog', [
'products' => Product::with('category')->paginate(24),
]);
}2. Не ганяйте запити на кожне натискання клавіші
{{-- Погано: запит на кожен символ --}}
<input wire:model.live="search">
{{-- Добре --}}
<input wire:model.live.debounce.400ms="search">3. Дробіть важкі сторінки на компоненти
Оновлення вкладеного компонента не перерендерює батьківський. Дашборд із шести незалежних віджетів працює помітно жвавіше, ніж один моноліт.
4. Кешуйте дорогі обчислення
#[Computed(persist: true, seconds: 600)]
public function monthlyRevenue(): array
{
return app(RevenueService::class)->byMonth();
}5. Стежте за N+1
// Погано — запит на кожен рядок у шаблоні
$orders = Order::paginate(50);
// Добре
$orders = Order::with(['customer', 'items.product'])->paginate(50);Увімкніть суворий режим у розробці, щоб N+1 падав з помилкою:
public function boot(): void
{
Model::preventLazyLoading(! app()->isProduction());
}6. wire:key у списках
Без ключів morph-алгоритм перебудовує більше вузлів, ніж потрібно, — і робить це неправильно.
7. Ліниве завантаження важких блоків
<livewire:revenue-chart lazy="on-scroll" />8. Обережно з polling
wire:poll.visible.10s замість wire:poll заощаджує серверу порядок величини
запитів.
| Симптом | Ймовірна причина |
|---|---|
| Затримка 300–800 мс на будь-яку дію | Важкий render() або N+1 |
| Величезний HTML сторінки | Колекції моделей у публічних властивостях |
| Зростає навантаження на PHP-FPM | Агресивний wire:poll або .live без debounce |
| Поля «стрибають» під час оновлення списку | Бракує wire:key |
| Перерендерюється вся сторінка | Усе в одному компоненті, немає розбиття |
23. Часті помилки та рішення
«Component must have a single root element»
У шаблоні кілька кореневих вузлів або текст/коментар на верхньому рівні. Загорніть усе в один <div>.
<h1>Заголовок</h1>
<p>Текст</p><div>
<h1>Заголовок</h1>
<p>Текст</p>
</div>«Livewire encountered corrupt data»
Не збігся підпис snapshot. Причини: змінився APP_KEY, сторінка відкрита з кешу після
деплою, дві вкладки з різними сесіями. Зазвичай лікується перезавантаженням сторінки; після деплою —
php artisan optimize:clear.
Кліки не працюють, консоль порожня
- Немає
@livewireScriptsу layout. - Підключено другий екземпляр Alpine.js (у Livewire 3 він уже всередині).
- Помилка JS вище на сторінці обірвала виконання.
- Елемент усередині
wire:ignore.
Значення полів «переїжджають» між рядками
Бракує wire:key/:key у циклі. Ключ має бути стабільним і унікальним.
@foreach ($rows as $row)
<div wire:key="row-{{ $row->id }}">…</div>
@endforeach«Unable to set component data. Public property not found»
Ви прив'язали wire:model до неіснуючої або не-публічної властивості. Перевірте
написання й модифікатор доступу.
«Cannot bind to model data without validation rules»
Прив'язка виду wire:model="post.title" потребує правила для post.title в
rules() або #[Validate].
Сторонній віджет ламається після оновлення
Select2, Flatpickr, TinyMCE і подібні самі змінюють DOM. Загорніть у wire:ignore і
синхронізуйте вручну.
<div wire:ignore x-data x-init="
const picker = flatpickr($refs.input, {
onChange: (dates, str) => $wire.set('date', str),
});
">
<input x-ref="input" type="text">
</div>Завантаження файлу мовчки падає
Перевірте upload_max_filesize і post_max_size у PHP, а також
client_max_body_size у nginx. Значення мають перевищувати ліміт у правилі max:.
Модальне вікно не закривається після збереження
Надішліть браузерну подію й обробіть її в Alpine, а не покладайтеся на перемальовування.
$this->dispatch('close-modal', name: 'order-form');Скрипти не працюють після wire:navigate
Перенесіть ініціалізацію з DOMContentLoaded у livewire:navigated.
Помилка 419 (Page Expired)
Сплила сесія. Збільште SESSION_LIFETIME або перехопіть статус у хуку
request і перезавантажте сторінку — приклад є в розділі 19.
24. Шпаргалка
Директиви Blade
| Директива | Призначення |
|---|---|
wire:model | Прив'язка поля до властивості (відкладена) |
wire:model.live | Прив'язка із запитом на кожну зміну |
wire:model.blur | Надсилання при втраті фокусу |
wire:click | Виклик методу по кліку |
wire:submit | Обробка надсилання форми |
wire:keydown.enter | Реакція на клавішу |
wire:change | Реакція на change |
wire:confirm | Підтвердження перед дією |
wire:loading | Індикатор завантаження |
wire:target | Звузити індикатор до дії |
wire:dirty | Є незбережені зміни |
wire:offline | Немає з'єднання |
wire:poll | Періодичне оновлення |
wire:init | Викликати метод одразу після рендеру |
wire:navigate | SPA-перехід за посиланням |
wire:key | Ідентифікатор елемента в циклі |
wire:ignore | Виключити піддерево з morph |
wire:transition | Анімація появи/зникнення |
wire:stream | Потокова віддача вмісту |
PHP-атрибути
| Атрибут | Призначення |
|---|---|
#[Validate] | Правило валідації властивості |
#[Locked] | Заборона зміни з фронтенду |
#[Computed] | Обчислювана властивість із кешем |
#[Url] | Синхронізація з рядком запиту |
#[Session] | Зберігання значення в сесії |
#[On] | Слухач події |
#[Reactive] | Оновлення параметра від батька |
#[Modelable] | Підтримка wire:model на компоненті |
#[Lazy] | Ліниве завантаження компонента |
#[Layout] | Layout для full-page компонента |
#[Title] | Заголовок сторінки |
#[Renderless] | Метод без перемальовування |
Корисні методи компонента
$this->reset(); // скинути всі властивості до початкових
$this->reset('search', 'page'); // скинути вибрані
$this->only('title', 'body'); // масив із частини властивостей
$this->except('password'); // усе, крім зазначеного
$this->fill(['title' => 'Новий']); // масове присвоєння
$this->pull('draft'); // отримати і скинути
$this->validate();
$this->validateOnly('email');
$this->resetValidation();
$this->addError('email', 'Повідомлення');
$this->dispatch('saved', id: $post->id);
$this->redirect('/orders', navigate: true);
$this->redirectRoute('orders.index', navigate: true);
$this->skipRender(); // не перемальовувати в цьому циклі
$this->js('alert("Готово")'); // виконати JS на клієнті
$this->stream(to: 'answer', content: $chunk);Artisan-команди
php artisan livewire:make Orders/OrderTable
php artisan livewire:make Counter --inline
php artisan livewire:form PostForm
php artisan livewire:attribute ValidPhone
php artisan livewire:publish --config
php artisan livewire:publish --assetsОфіційні ресурси
- Документація Livewire: livewire.laravel.com/docs
- Документація Laravel: laravel.com/docs
- Alpine.js: alpinejs.dev