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

Livewire і Filament

20 питань · ~20 хв · Версія v3.0

Увійдіть, щоб продовжити

Livewire 4 (стан компонента, однофайлові компоненти, острови, відкладене завантаження, файли, безпека) і Filament 5 (ресурси, форми й таблиці, авторизація, мультитенантність, тестування).

За спробу
20
У пулі
100
Проходжень
0
Середній бал
-
Пройшли на 70%+
-

Питання для підготовки

113 питань

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

Ресурс - це опис того, як модель виглядає в адмінці: таблиця, форма й сторінки навколо них.

php artisan make:filament-resource Vacancy --generate

Команда створює клас ресурсу, сторінки List/Create/Edit, а форму й таблицю виносить в окремі файли (Schemas/VacancyForm.php, Tables/VacancyTable.php). Прапорці --embed-schemas і --embed-table лишають їх усередині класу ресурсу.

Дві головні частини - форма й таблиця:

public static function form(Schema $schema): Schema
{
    return $schema->components([
        TextInput::make('title')
            ->required()
            ->maxLength(255),
        Select::make('level')
            ->options(VacancyLevel::class)
            ->required(),
        Toggle::make('is_published'),
    ]);
}

public static function table(Table $table): Table
{
    return $table
        ->columns([
            TextColumn::make('title')->searchable()->sortable(),
            TextColumn::make('company.name')->label('Компанія'),
            IconColumn::make('is_published')->boolean(),
        ])
        ->filters([
            SelectFilter::make('level')->options(VacancyLevel::class),
        ]);
}

Що це дає без додаткового коду: список із пошуком, сортуванням і пагінацією, форми створення й редагування з валідацією, масові дії, фільтри.

На чому побудовано: Livewire відповідає за реактивність без написання JS, Alpine - за дрібні взаємодії в браузері, Tailwind - за оформлення. Тому Filament - це PHP-код, а не окремий фронтенд-застосунок.

Що варто знати одразу: форма й таблиця - це звичайні PHP-масиви обʼєктів, тож видимість поля, опції списку чи доступність дії задаються замиканнями й можуть залежати від користувача та стану запису.

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

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