Питання на співбесіді з Livewire і Filament
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
113 питань
Ресурс - це опис того, як модель виглядає в адмінці: таблиця, форма й сторінки навколо них.
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-масиви обʼєктів, тож видимість поля, опції списку чи доступність дії задаються замиканнями й можуть залежати від користувача та стану запису.
Кожна дія 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.
Livewire - фреймворк для Laravel, що дає змогу будувати інтерактивні інтерфейси на PHP і Blade, без окремого фронтенду на Vue чи React. Компонент - це PHP-клас зі станом (публічні властивості) і діями (публічні методи) плюс Blade-шаблон.
new class extends Component {
public int $count = 0;
public function increment(): void
{
$this->count++;
}
};
<div>
<span>{{ $count }}</span>
<button wire:click="increment">+</button>
</div>
Що відбувається при кліку:
- JavaScript Livewire перехоплює клік і надсилає AJAX-запит на сервер: знімок поточного стану компонента (
count: 0) і назву методу (increment); - сервер заново створює компонент зі знімка - між запитами на сервері нічого не зберігається;
- викликає
increment(), стан стаєcount: 1; - рендерить Blade-шаблон у HTML і формує новий знімок;
- браузер отримує HTML і морфить DOM - змінює лише те, що відрізняється, не перебудовуючи сторінку. Фокус, позиція курсору й стан Alpine зберігаються.
Що з цього випливає:
- кожна дія - мережевий запит. Миттєвих речей на кшталт розкриття меню через сервер не роблять - для цього є Alpine, вбудований у Livewire;
- публічні властивості видно в браузері (вони в знімку), а викликати можна будь-який публічний метод - тому дії треба авторизовувати як звичайні HTTP-запити;
- перший рендер серверний - сторінка приходить готовим HTML, що добре для SEO і швидкості першого показу.
Підключення: у Livewire 4 скрипти й стилі додаються автоматично на сторінки з компонентами. Alpine окремо підключати не треба - він уже в складі Livewire.
Livewire 4 підтримує три формати одного й того самого компонента.
1. Однофайловий (SFC) - за замовчуванням:
php artisan make:livewire post.create
# resources/views/components/post/⚡create.blade.php
<?php
use Livewire\Component;
new class extends Component {
public string $title = '';
public function save(): void { /* ... */ }
};
?>
<div>
<input wire:model="title">
<button wire:click="save">Зберегти</button>
</div>
PHP-клас (анонімний) і шаблон в одному файлі. Емодзі ⚡ у назві - щоб компоненти було видно в дереві файлів; його можна вимкнути в config/livewire.php (make_command.emoji).
2. Багатофайловий (MFC) - --mfc:
resources/views/components/post/⚡create/
├── create.php # клас
├── create.blade.php # шаблон
├── create.js # JavaScript (необов'язково)
├── create.css # стилі компонента (необов'язково)
└── create.test.php # Pest-тест (з --test)
3. Класовий (як у Livewire 3) - --class: клас в app/Livewire/..., шаблон у resources/views/livewire/....
Коли що:
- однофайловий - більшість компонентів: усе поруч, легко читати;
- багатофайловий - великі компоненти з помітною кількістю JavaScript чи стилів, краща навігація в IDE;
- класовий - звичний для команд з Livewire 3, якщо проєкт уже так організований, або коли на класи посилаються з іншого PHP-коду.
Перемикання між форматами - php artisan livewire:convert post.create (з --mfc чи --sfc).
Сторінки зручно тримати окремо: make:livewire pages::post.create створює компонент у resources/views/pages/.
Найважливіше правило - узгодженість: перед створенням компонента подивитися, який формат і які каталоги вже використовує проєкт, і не змішувати стилі без причини.
wire:model зв'язує поле форми з публічною властивістю компонента. Але за замовчуванням не надсилає запит при введенні: значення оновлюється в браузері й потрапляє на сервер разом з наступною дією (wire:click, wire:submit).
<form wire:submit="save">
<input wire:model="title"> {{-- піде на сервер при відправці форми --}}
<button type="submit">Зберегти</button>
</form>
Для більшості форм це саме те, що треба: жодного запиту на кожну літеру.
wire:model.live - надсилає оновлення під час введення, з затримкою (debounce) 150 мс: запит іде, коли користувач зупинився.
<input wire:model.live="search"> {{-- живий пошук --}}
<input wire:model.live.debounce.500ms="search"> {{-- рідше --}}
Модифікатори часу синхронізації в Livewire 4:
| Модифікатор | Коли оновлюється значення |
|---|---|
.live |
під час введення, з запитом |
.blur |
при втраті фокуса |
.change |
на подію change (зручно для select) |
.enter |
при натисканні Enter |
.lazy |
на change з запитом (сумісність з v3) |
Пастка v4: .blur, .change і .enter самі по собі лише оновлюють значення в браузері. Щоб при цьому ще й піти запит на сервер, їх поєднують з .live: wire:model.blur.live="email" - класичний варіант для валідації поля при виході з нього.
Інші модифікатори: .number і .boolean - приведення типу на сервері, .fill - взяти початкове значення з атрибута value в HTML, .throttle.Xms - не частіше, ніж раз на X мс.
Що варто знати:
select, що залежить від іншого (міста після області), потребуєwire:keyна залежному полі, інакше вибране значення не скинеться;- у
textareaне треба виводити{{ $content }}всередині - значення підставитьwire:model; wire:modelу Livewire 4 реагує лише на події самого елемента, а не на спливаючі з дочірніх (для старої поведінки -.deep);- живе оновлення коштує - кожен запит відтворює компонент і рендерить шаблон. Для полів, що не впливають на інші частини екрана,
.liveне потрібен.
Компонент може бути не лише частиною сторінки, а цілою сторінкою - без контролера.
Маршрут у Livewire 4 - через Route::livewire():
// routes/web.php
Route::livewire('/posts/create', 'pages::post.create');
Route::livewire('/posts/{post}', 'pages::post.show');
Макет. Сторінка рендериться всередині макета - за замовчуванням layouts::app (resources/views/layouts/app.blade.php), де має бути {{ $slot }}. Створити його: php artisan livewire:layout. Змінити глобально - ключ component_layout у config/livewire.php; для окремого компонента - атрибут:
new #[Layout('layouts::dashboard')] class extends Component { /* ... */ };
Заголовок сторінки - атрибут #[Title], а в макеті <title>{{ $title ?? config('app.name') }}</title>:
new #[Title('Нова публікація')] class extends Component { /* ... */ };
Для динамічного заголовка - ->title(...) у методі render():
public function render()
{
return $this->view()->title("Редагування: {$this->post->title}");
}
Параметри маршруту приходять у mount() за назвою, працює і прив'язка моделей:
new class extends Component {
public Post $post;
public function mount(Post $post): void
{
$this->authorize('update', $post);
$this->post = $post;
}
};
Інші слоти макета (наприклад, $lang чи мета-теги) задаються через <x-slot:lang> поза кореневим елементом шаблону компонента.
Що варто пам'ятати:
mount()виконується лише під час першого завантаження сторінки. Перевірка доступу вmount()не захищає наступні дії компонента - їх авторизують окремо;- middleware маршруту (
auth,can:...) застосовуються як до звичайних маршрутів; - у Livewire 3 сторінки реєстрували через
Route::get('/posts/create', CreatePost::class), а ключ конфігурації називавсяlayout- при оновленні до v4 це змінюється.
wire:submit на тегу <form> перехоплює відправку форми й викликає метод компонента замість звичайного POST-запиту з перезавантаженням сторінки.
new class extends Component {
public string $title = '';
public string $content = '';
public function save(): void
{
$validated = $this->validate([
'title' => 'required|min:3',
'content' => 'required',
]);
auth()->user()->posts()->create($validated);
$this->redirectRoute('posts.index');
}
};
<form wire:submit="save">
<input type="text" wire:model="title">
<textarea wire:model="content"></textarea>
<button type="submit">Опублікувати</button>
</form>
Що робить Livewire автоматично:
preventDefault()- модифікатор.preventне потрібен, на відміну від інших подій;- блокує форму на час запиту: кнопки відправки стають неактивними, поля - доступними лише для читання. Подвійне натискання не створить два пости;
- значення з
wire:modelпотрапляють на сервер разом із запитом на відправку - окремі запити на кожне поле не потрібні.
Параметри методу можна передати прямо в директиві: wire:submit="save('draft')".
Чому type="submit" важливий: кнопка без типу всередині форми теж відправляє її, а кнопка «Скасувати» з wire:click усередині форми має бути type="button", інакше натискання на неї відправить форму.
Enter у полі відправляє форму, як у звичайному HTML, - це правильна поведінка для доступності, і її не варто ламати.
Порівняно з контролером: логіка така сама - валідація, авторизація, збереження, редирект, - але без окремого маршруту й з помилками валідації, що з'являються без перезавантаження сторінки. Метод save() - публічна дія, яку можна викликати з браузера будь-коли, тож перевірки прав мають бути всередині нього.
Валідація в Livewire побудована на звичайній валідації Laravel - ті самі правила й повідомлення.
Спосіб 1 - $this->validate() з правилами:
public function save(): void
{
$validated = $this->validate([
'title' => 'required|min:3|max:255',
'email' => 'required|email',
]);
Post::create($validated);
}
Якщо дані невалідні, метод кидає виняток, виконання дії зупиняється, а помилки повертаються в браузер. validate() повертає лише перевірені поля - їх безпечно передавати в create().
Спосіб 2 - атрибут #[Validate] над властивістю:
use Livewire\Attributes\Validate;
new class extends Component {
#[Validate('required|min:3')]
public string $title = '';
#[Validate('required|email', as: 'електронна адреса')]
public string $email = '';
public function save(): void
{
$this->validate(); // правила беруться з атрибутів
}
};
Правила поруч із властивістю, і поле перевіряється автоматично при кожному оновленні з браузера (якщо воно надсилається на сервер - з .live).
Показ помилок - звичайні директиви Blade:
<input wire:model="title" @error('title') aria-invalid="true" @enderror>
@error('title')
<p class="text-sm text-red-600">{{ $message }}</p>
@enderror
Важливо: $this->validate() перед збереженням потрібен навіть з #[Validate] - атрибут перевіряє лише поля, які оновлювалися. Поле, якого користувач не торкався, без явного validate() залишиться неперевіреним.
Обмеження атрибутів: у PHP-атрибутах не можна використовувати об'єкти правил (Rule::unique(...), Password::defaults()). Для них - метод rules().
Українські повідомлення - через файли перекладів lang/uk/validation.php, як і в усьому Laravel; назву поля задає параметр as: чи метод validationAttributes().
Ручне керування помилками: $this->addError('email', 'Ця адреса вже зайнята'), $this->resetValidation('email').
Livewire відстежує, чи відрізняється стан у браузері від стану на сервері. Форма, в якій користувач щось змінив, але це ще не відправлено, вважається «брудною» (dirty).
wire:dirty показує елемент лише тоді, коли є незбережені зміни:
<form wire:submit="update">
<input wire:model="title">
<textarea wire:model="content"></textarea>
<div wire:dirty class="text-amber-600">Є незбережені зміни</div>
<button type="submit">Зберегти</button>
</form>
Щойно користувач почав вводити - повідомлення з'являється; після відправки форми й синхронізації з сервером - зникає.
Варіанти:
wire:dirty.remove- навпаки: показувати елемент, доки змін немає («Усе збережено»);wire:dirty.class="border-amber-500"- додати клас замість показу елемента;wire:target="title"- стежити лише за конкретною властивістю:
<input wire:model.live.blur="title" wire:dirty.class="border-amber-500" wire:target="title">
$dirtyу виразах і$wire.$dirty('email')в Alpine - перевірити програмно:
<button x-bind:disabled="! $wire.$dirty()">Зберегти</button>
Кнопка «Зберегти» активна лише тоді, коли є що зберігати.
Що важливо розуміти: «чисто» означає «синхронізовано із сервером», а не «збережено в базу». Якщо поле з wire:model.live уже відправило значення на сервер, а метод збереження ще не викликано, wire:dirty уже не покаже змін. Для форм, де важливо саме збереження в базу, стан «змінено» краще вести явно (наприклад, порівнювати з початковими значеннями).
Корисне доповнення: попередження при спробі піти зі сторінки з незбереженими змінами - обробник beforeunload в Alpine, що перевіряє $wire.$dirty().
wire:confirm показує діалог підтвердження перед виконанням дії. Якщо користувач натиснув «Скасувати» чи Escape, запит на сервер не надсилається.
<button
type="button"
wire:click="delete({{ $post->id }})"
wire:confirm="Видалити цей пост? Дію не можна скасувати."
>
Видалити
</button>
Працює з будь-якою дією: wire:click, wire:submit тощо.
Підтвердження введенням тексту - для особливо руйнівних дій (видалення облікового запису чи проєкту). Модифікатор .prompt, очікуваний текст - після |:
<button
wire:click="deleteAccount"
wire:confirm.prompt="Щоб підтвердити, введіть ВИДАЛИТИ|ВИДАЛИТИ"
>
Видалити обліковий запис
</button>
Дія виконається лише якщо введено точно ВИДАЛИТИ (з урахуванням регістру).
Що варто знати:
- це стандартні діалоги браузера (
confirm()/prompt()): виглядають по-різному в різних браузерах і не стилізуються. Для фірмового вигляду роблять власне модальне вікно (Alpine чи готові компоненти на кшталт Flux), а дію викликають з кнопки всередині нього; - підтвердження - це UX, а не захист. Метод
delete()можна викликати напряму з консолі браузера ($wire.delete(5)), минаючи діалог. Перевірка прав і власності запису обов'язкова в самому методі:
public function delete(Post $post): void
{
$this->authorize('delete', $post);
$post->delete();
}
- для масових чи незворотних операцій варто подумати про «м'яке» видалення (
SoftDeletes) і можливість відновити - це надійніше за будь-які діалоги; - текст діалогу - звичайний рядок, тож його варто перекладати так само, як решту інтерфейсу (
wire:confirm="{{ __('...') }}").
Після кожного запиту Livewire не замінює HTML компонента повністю, а морфить DOM: порівнює старе й нове дерево елемент за елементом і змінює лише відмінності. Так зберігаються фокус, введений текст, стан Alpine.
Проблема зі списками. Без підказок морфінг зіставляє елементи за позицією. Видалили перший елемент списку - і алгоритм вирішує, що змінився вміст кожного елемента, а останній зник:
@foreach ($todos as $todo)
<li>
<input type="checkbox" wire:model="done.{{ $todo->id }}">
<span x-data="{ editing: false }">{{ $todo->title }}</span>
</li>
@endforeach
Наслідки: стан Alpine (editing), фокус і введення «переїжджають» на сусідні рядки, анімації спрацьовують не там, а вкладені Livewire-компоненти отримують чужі дані.
wire:key дає кожному елементу стабільний ідентифікатор - морфінг зіставляє елементи за ключем:
@foreach ($todos as $todo)
<li wire:key="todo-{{ $todo->id }}">...</li>
@endforeach
@foreach ($posts as $post)
<livewire:post-card :$post :wire:key="$post->id" />
@endforeach
Правила:
- ключ - ідентифікатор даних (
id), а не індекс циклу.$loop->indexне допомагає: після видалення елемента індекси зсуваються так само; - ключ має бути унікальним у межах компонента. Якщо на сторінці два списки з тих самих моделей, додайте префікс:
"comment-{{ $id }}","reply-{{ $id }}"; - для вкладених Livewire-компонентів у циклі ключ обов'язковий - без нього стан дочірніх компонентів змішується.
Налаштування smart_wire_keys (у Livewire 4 увімкнено за замовчуванням) допомагає з ключами глибоко вкладених компонентів, але не звільняє від wire:key у циклах.
Ще один випадок - залежні поля: <select> міст, що змінюється разом з обраною областю, потребує wire:key="{{ $regionId }}", щоб значення коректно скинулося.
Альтернатива морфінгу для складних випадків - wire:replace: замінити вміст елемента повністю замість порівняння.
wire:poll періодично надсилає запит до компонента й оновлює його - найпростіший спосіб показувати свіжі дані без WebSocket.
<div wire:poll>
Нових замовлень: {{ $this->newOrdersCount }}
</div>
<div wire:poll.15s="refreshStatus">
Статус імпорту: {{ $status }}
</div>
За замовчуванням - кожні 2,5 секунди з повторним рендером компонента; з назвою методу - викликається цей метод.
Чому це дорого: тисяча відкритих вкладок з інтервалом 2,5 с - 400 запитів на секунду на сервер, кожен з гідрацією компонента, запитами до бази й рендером. Для PHP-FPM це сотні зайнятих процесів лише на опитування.
Як зменшити навантаження:
- довший інтервал. Найефективніший і найпростіший крок:
.10s,.1mзамість типових 2,5 с; .visible- опитувати лише тоді, коли елемент видно на екрані. Блок унизу довгої сторінки не опитується, поки до нього не прокрутять;- фонові вкладки Livewire пригальмовує автоматично - на 95%. Модифікатор
.keep-aliveвимикає це, і без справжньої потреби його не варто ставити; - легкий метод опитування: перевірити дешеву ознаку змін (кількість,
max(updated_at)) і не перебудовувати важкі дані, якщо нічого не змінилося; - острівець (
@island) з опитуванням - оновлюється лише ця частина компонента, а не весь шаблон; #[Isolate]- щоб повільне опитування одного компонента не затримувало запити інших.
Livewire 4: опитування більше не блокує інші дії - клік користувача не чекає на запит опитування в черзі.
Коли опитування - неправильний інструмент:
- оновлення потрібні миттєво (чат, спільне редагування) - WebSocket (Laravel Reverb + Echo), і Livewire слухає події трансляції через
#[On('echo:...')]; - оновлення рідкісні, а відвідувачів багато - теж краще події, ніж тисячі порожніх запитів;
- зміна станеться один раз (завершення імпорту) - опитування з зупинкою: перестаньте рендерити
wire:poll, коли статус фінальний.
Компонент з повільним запитом (статистика, звіт, зовнішній API) затримує всю сторінку: сервер не відправить HTML, поки не виконає mount() кожного компонента.
Два способи відкласти:
<livewire:revenue-chart lazy /> {{-- коли стане видимим на екрані --}}
<livewire:revenue-chart defer /> {{-- одразу після завантаження сторінки --}}
lazy- компонент завантажується, коли потрапляє в область видимості (прокрутили до нього). Для блоків унизу сторінки, які можуть і не знадобитися;defer- завантажується одразу після першого показу сторінки, незалежно від прокрутки. Для того, що потрібно завжди, але не повинно затримувати першу відповідь.
Сторінка приходить швидко з заглушкою на місці компонента, а сам компонент підвантажується окремим запитом.
Заглушка (placeholder) - що бачить користувач, поки компонент вантажиться:
@placeholder
<div class="h-64 animate-pulse rounded bg-gray-100"></div>
@endplaceholder
<div>
Дохід цього місяця: {{ $amount }}
</div>
У класових компонентах - метод placeholder(). Кореневий тег заглушки й компонента має збігатися (<div> і <div>). Добра заглушка має той самий розмір, що й готовий блок, - інакше сторінка «стрибатиме».
Зробити відкладеним завжди - атрибути на класі #[Lazy] чи #[Defer]. Для цілої сторінки - Route::livewire(...)->lazy().
Об'єднання запитів. За замовчуванням кожен відкладений компонент вантажиться окремим паралельним запитом. Десять віджетів на дашборді - десять запитів. Можна об'єднати в один:
<livewire:revenue lazy.bundle />
Але тоді повільний віджет затримає швидкі - об'єднання доречне для схожих за швидкістю компонентів.
Що варто знати:
- параметри, передані відкладеному компоненту (
:user="$user"), серіалізуються й чекають у браузері до завантаження - тож моделі в них перезавантажуються з бази; - SEO: вміст відкладеного компонента відсутній у початковому HTML;
- у тестах -
Livewire::withoutLazyLoading(), щоб бачити справжній вміст, а не заглушку; - для частини компонента, а не цілого компонента, - острівець
@island(lazy: true).
Обчислювана властивість - метод з атрибутом #[Computed], до якого звертаються як до властивості. Результат кешується в межах одного запиту.
use Livewire\Attributes\Computed;
new class extends Component {
public string $search = '';
#[Computed]
public function users()
{
return User::query()
->where('name', 'like', "%{$this->search}%")
->with('team')
->limit(20)
->get();
}
};
@foreach ($this->users as $user)
<li wire:key="{{ $user->id }}">{{ $user->name }} ({{ $user->team->name }})</li>
@endforeach
<p>Знайдено: {{ $this->users->count() }}</p> {{-- повторний доступ - без нового запиту --}}
У шаблоні - лише через $this->: $this->users, а не $users.
Чим краще за публічну властивість (public $users заповнена в mount()):
- не потрапляє в знімок стану - не їде в браузер і назад з кожним запитом;
- не перезавантажується з бази на кожен запит гідрації - лише коли шаблон чи код справді звертається до неї;
- завантажені зв'язки (
with('team')) працюють, бо запит виконується повністю щоразу, а не відновлюється за ключами; - завжди актуальна - похідна від поточного стану (
$search), її не треба оновлювати вручну.
Ліниве виконання - головна перевага: якщо дані потрібні лише в гілці @if, запит виконається лише тоді, коли умова істинна. А в острівці - лише коли рендериться саме острівець.
Скинути кеш у межах запиту, якщо дані змінилися:
public function addUser(): void
{
User::create([...]);
unset($this->users); // наступне звернення виконає запит заново
}
Кешування між запитами - #[Computed(persist: true)] (на компонент, година за замовчуванням) чи #[Computed(cache: true)] (спільно для всіх екземплярів) - але з обережністю щодо персональних даних.
Правило: публічні властивості - для стану, який змінює користувач (фільтри, введені значення, обраний id). Дані, що з них виводяться, - обчислювані властивості.
Компоненти на сторінці незалежні й не знають одне про одного. Події - спосіб повідомити інших, що щось сталося, не створюючи прямої залежності.
Відправити подію з PHP:
public function save(): void
{
$post = Post::create($this->form->all());
$this->dispatch('post-created', postId: $post->id);
}
Слухати - атрибут #[On] над методом:
use Livewire\Attributes\On;
new class extends Component {
#[On('post-created')]
public function refreshList(int $postId): void
{
unset($this->posts); // скинути кеш обчислюваної властивості
}
};
Параметри події передаються в метод за назвами.
Кому дістанеться подія:
- за замовчуванням - усім компонентам на сторінці, що слухають цю назву;
- конкретному компоненту -
$this->dispatch('post-created')->to(Dashboard::class); - лише самому собі -
$this->dispatch('post-created')->to(self: true).
Події з шаблону - без власного методу-посередника:
<button wire:click="$dispatch('open-modal', { id: {{ $post->id }} })">Редагувати</button>
<button wire:click="$dispatchTo('posts', 'refresh')">Оновити список</button>
Подія від дочірнього компонента до батька можна слухати прямо на тегу:
<livewire:edit-post :$post @saved="close" />
Як це працює всередині: подія з PHP повертається в браузер разом з відповіддю, а вже там Livewire надсилає запити компонентам-слухачам. Тобто подія - це щонайменше два запити, а не виклик методу на сервері.
Можливо, подія не потрібна:
- дитина хоче викликати метод батька -
wire:click="$parent.remove({{ $id }})"; - дитина має оновлюватися зі зміною даних батька -
#[Reactive]; - обидві частини в одному компоненті - острівці.
Назви подій - у стилі kebab-case з «простором імен» (cart-updated, post-created), щоб не зіткнутися з подіями сторонніх бібліотек.
Питання з реальних технічних співбесід - 113 питань у 8 темах, розібраних із відповідями. Нижче - розбивка за рівнями та темами, якщо хочете звузити підготовку.
- Теми
- Livewire 21 Filament 19 Дії, інфолисти й віджети 13 Продуктивність Livewire 12 Події, Alpine і навігація 12 Таблиці Filament 12 Форми й схеми Filament 12 Форми й валідація 12
Готуєтесь до співбесіди не просто так: зараз на сайті 144 відкритих вакансій Laravel і PHP. Переглянути вакансії