Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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: морфінг

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 посеред проєкту дороге - тому питання «чи буде мобільний застосунок» варто поставити на початку.

Докладніше в документації: Livewire: швидкий старт

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 за стабільним ідентифікатором, а не індексом;
  • великий масив у властивості - весь масив серіалізується в кожен запит. Для сотень рядків краще окремі дочірні компоненти чи редагування по одному.

Докладніше в документації: wire:model

Кожна дія 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.

Докладніше в документації: wire:loading

Прочитати - ще не значить знати

20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.