Livewire 4: компоненти й стан
20 питань · ~20 хв · Версія v3.0
Увійдіть, щоб продовжити
Властивості й безпека стану, дії й рендеринг, сторінки-компоненти, острови й ліниве завантаження, події, потокова передача й Alpine - питання всіх рівнів, від junior до senior.
- За спробу
- 20
- У пулі
- 62
- Проходжень
- 0
- Середній бал
- -
- Пройшли на 70%+
- -
Питання для підготовки
27 питаньFilament - фреймворк Server-Driven UI для Laravel: інтерфейси (адмінки, панелі) описуються на PHP через структуровані об'єкти, а не верстку.
public static function form(Schema $schema): Schema
{
return $schema->components([
TextInput::make('title')->required(),
Select::make('status')->options(Status::class),
]);
}
- Побудований на Livewire, Alpine.js і Tailwind CSS.
- Будівельні блоки: Resources, Forms, Tables, Actions, Infolists, Widgets.
- Компоненти ініціалізуються статичними
make()-методами; динамічні значення задаються замиканнями з утилітамиGet/Set.
Після кожної дії сервер повертає новий HTML компонента. Livewire не замінює ним старий цілком, а морфить: проходить обидва дерева елемент за елементом і змінює лише те, що відрізняється. Завдяки цьому зберігаються фокус, введений текст, позиція прокрутки й обробники подій.
Де морфінг помиляється. Алгоритм порівнює елементи за позицією. Якщо між ними з'являється новий елемент, він може «зсунути» відповідність:
<div><input wire:model="title"></div>
@if ($errors->has('title'))
<div>{{ $errors->first('title') }}</div>
@endif
<div><button>Зберегти</button></div>
Коли з'являється помилка, Livewire бачить на другому місці новий div і може вирішити, що це змінений старий, - результат: зайві перерисовки, втрачений стан сторонніх елементів. Livewire додає в шаблон службові маркери навколо @if і @foreach, щоб це згладити, але найнадійніший інструмент - ключі.
wire:key - ідентичність елемента:
@foreach ($todos as $todo)
<li wire:key="todo-{{ $todo->id }}">...</li>
@endforeach
Morph зіставляє елементи за ключем, а не за позицією: видалення першого рядка не змушує «перейменовувати» всі наступні. Для дочірніх компонентів у циклі ключ обов'язковий.
wire:ignore - не чіпати вміст елемента взагалі:
<div wire:ignore>
<div x-init="new Chart($el, ...)"></div>
</div>
Для сторонніх бібліотек (редактори, графіки, карти), які самі керують своїм DOM: інакше Livewire «виправить» їхні зміни назад до HTML з сервера. wire:ignore.self - ігнорувати атрибути самого елемента, але оновлювати дітей.
wire:replace - протилежне: не морфити, а замінити вміст повністю:
<div wire:replace>
<json-viewer>@json($payload)</json-viewer>
</div>
Для веб-компонентів і елементів зі своїм внутрішнім станом, який треба скинути при кожному оновленні. wire:replace.self - замінити й сам елемент.
Типові симптоми й причини:
| Симптом | Причина |
|---|---|
| введене «переїжджає» в інше поле | немає wire:key у циклі |
| сторонній віджет зникає чи ламається після дії | немає wire:ignore |
| елемент зберігає старий стан, хоча дані змінилися | морф перевикористав елемент - wire:key зі зміною ключа чи wire:replace |
| дочірній компонент не перестворюється | той самий wire:key - змінити ключ, щоб Livewire створив новий екземпляр |
Продуктивність: морфінг великого HTML (таблиця на тисячі рядків) коштує і на сервері (рендер), і в браузері (порівняння). Острівці, ліниві компоненти й пагінація зменшують обсяг, який морфиться за одну дію.
Livewire сильний там, де інтерфейс - це форми, таблиці, фільтри й адмінки поверх серверних даних: логіка й валідація в PHP, без окремого API і дублювання правил на фронтенді. Але кожна його взаємодія - це запит на сервер і рендер компонента, і з цього випливають межі.
Коли Livewire - поганий вибір:
- миттєва реакція на кожен рух: перетягування з анімацією, малювання, редактори з частими змінами, ігри. Затримка мережі (навіть 50-100 мс) помітна;
- робота офлайн чи на нестабільному з'єднанні - без сервера інтерфейс не працює;
- складний клієнтський стан, що майже не стосується сервера: багатокрокові конструктори, полотна, планувальники з десятками елементів на екрані;
- кілька клієнтів одного API: мобільний застосунок і вебверсія - API все одно потрібен, і Livewire додає другий шлях до тих самих даних;
- висока кількість одночасних користувачів з частими діями: кожна дія навантажує PHP-воркери, тоді як SPA ходить на сервер лише за даними.
Що обрати замість:
| Ситуація | Інструмент |
|---|---|
| дрібна інтерактивність без сервера: меню, вкладки, модальні вікна | Alpine.js - разом з Livewire або без нього |
| багатий інтерфейс на React чи Vue, але маршрутизація й дані з Laravel без окремого API | Inertia |
| кілька клієнтів, офлайн, окрема фронтенд-команда | SPA чи мобільний застосунок + API (Sanctum) |
| сайт зі статичним вмістом | звичайний Blade, кеш на краю мережі |
Поєднання частіше, ніж вибір:
- Livewire + Alpine: клієнтські дрібниці на Alpine, дані й збереження на Livewire.
$wireу Alpine дає доступ до властивостей і методів компонента; - острівці й ізоляція: важку частину сторінки винести в окремий компонент чи острівець, щоб її оновлення не перерендерювали все;
- окремий віджет на JavaScript (графік, редактор) усередині Livewire-сторінки з
wire:ignore.
Як зважувати: склад команди (PHP-розробники чи фронтенд), вимоги до швидкості реакції, кількість клієнтів API. Переписування з Livewire на SPA посеред проєкту дороге - тому питання «чи буде мобільний застосунок» варто поставити на початку.
wire:model приймає не лише ім'я властивості, а й шлях усередині неї через крапку.
Масиви й вкладені дані:
public array $address = ['city' => '', 'street' => ''];
public array $items = [];
public array $roles = [];
<input wire:model="address.city">
<input wire:model="address.street">
@foreach ($items as $index => $item)
<div wire:key="item-{{ $item['id'] }}">
<input wire:model="items.{{ $index }}.qty">
</div>
@endforeach
{{-- кілька чекбоксів у масив --}}
<input type="checkbox" value="editor" wire:model="roles">
<input type="checkbox" value="author" wire:model="roles">
Хук updated отримує повний шлях: updatedItems($value, $key) з $key = '2.qty' - видно, який рядок змінився.
Об'єкти форм - те саме, але властивості типізовані й валідація поруч:
<input wire:model="form.title">
Чому не модель напряму. У Livewire 3 і 4 цей код кидає виняток:
public Post $post;
<input wire:model="post.title"> {{-- Can't set model properties directly --}}
Причини:
- безпека: властивості компонента - це дані, які надсилає браузер. Зв'язування з моделлю дозволило б змінювати будь-який атрибут моделі запитом, включно з тими, що не виводяться у формі (
is_admin,user_id), - аналог масового присвоєння без$fillable; - стан: модель серіалізується лише як клас і ключ, а при кожному запиті завантажується з бази заново - незбережені зміни атрибутів між запитами губилися б.
Як правильно: окремі властивості чи об'єкт форми, а при збереженні - явне заповнення провалідованими даними:
public function save(): void
{
$this->post->update($this->form->validate());
}
Стару поведінку можна ввімкнути (legacy_model_binding у конфігурації разом з правилами валідації для кожного поля), але для нового коду це не рекомендований шлях.
Пастки з масивами:
- без
wire:keyу циклі після видалення рядка введені значення «переїжджають» в сусідні поля; - індекси масиву після видалення:
unset($this->items[$i])лишає дірку в ключах;array_values()після видалення - іwire:keyза стабільним ідентифікатором, а не індексом; - великий масив у властивості - весь масив серіалізується в кожен запит. Для сотень рядків краще окремі дочірні компоненти чи редагування по одному.
Кожна дія Livewire (wire:click, відправка форми, оновлення wire:model.live) - це запит на сервер. Поки він іде, користувач має бачити, що щось відбувається.
wire:loading показує елемент лише під час запиту:
<button wire:click="save">Зберегти</button>
<span wire:loading wire:target="save">Зберігаємо...</span>
Корисні модифікатори:
wire:target="save"- реагувати лише на конкретну дію чи властивість, а не на будь-який запит компонента.wire:loading.attr="disabled"- вимкнути кнопку на час запиту, щоб не було подвійних відправок.wire:loading.class="opacity-50"- додати клас.wire:loading.remove- навпаки, приховати елемент під час запиту.wire:loading.delay- показати індикатор лише якщо запит триває довше ~200 мс, щоб швидкі дії не «блимали».
<button wire:click="save" wire:loading.attr="disabled" wire:loading.class="opacity-50">
Зберегти
</button>
У Livewire 4 елементи, що ініціюють запит, ще й автоматично отримують атрибут data-loading на час запиту. Тож стилізувати стан можна просто через Tailwind: class="data-loading:opacity-50", без wire:target.
Спершу почитати
Прочитати - ще не значить знати
20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.