Junior: питання на співбесіді з теми «Події, Alpine і навігація»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
Компоненти на сторінці незалежні й не знають одне про одного. Події - спосіб повідомити інших, що щось сталося, не створюючи прямої залежності.
Відправити подію з 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), щоб не зіткнутися з подіями сторонніх бібліотек.
wire:navigate на посиланні перетворює звичайний перехід на SPA-навігацію: сторінка завантажується через fetch, а браузер не перезавантажується.
<nav>
<a href="/" wire:navigate>Головна</a>
<a href="/jobs" wire:navigate>Вакансії</a>
<a href="/posts" wire:navigate.hover>Блог</a>
</nav>
Що відбувається при кліку:
- Livewire перехоплює клік і запитує нову сторінку звичайним GET-запитом;
- замінює вміст
<body>і оновлює<head>(заголовок, мета-теги); - змінює адресу через History API - кнопки «Назад» і «Вперед» працюють;
- показує смугу прогресу вгорі сторінки.
Чому швидше: CSS і JavaScript не завантажуються й не виконуються заново, Alpine і Livewire не ініціалізуються з нуля. Сторінки відкриваються помітно швидше, і застосунок відчувається як SPA, хоча сервер віддає звичайний HTML.
Попереднє завантаження:
- за замовчуванням Livewire починає запит, щойно користувач натиснув кнопку миші, - до того, як він її відпустив;
.hover- починати завантаження після 60 мс наведення чи фокусу на посиланні. Ще швидше для користувача, але сервер отримує запити на сторінки, які можуть і не знадобитися.
Корисні можливості:
$this->redirect('/posts', navigate: true)- SPA-перехід після дії компонента;@persist('player')- елемент (аудіоплеєр, бічна панель) переживає перехід без перестворення;- активне посилання - атрибут
data-currentна посиланні поточної сторінки чи директиваwire:current="font-bold"; - анімація переходу -
wire:transition.navigateчерез View Transitions API браузера.
Що варто знати:
- сервер віддає повну сторінку, як звичайно, - маршрути, middleware, SEO не змінюються. Без JavaScript посилання працюють як звичайні;
- JavaScript сторінок треба адаптувати:
DOMContentLoadedспрацьовує лише на першому завантаженні - замість ньогоlivewire:navigated; - гостьові сторінки з кешуванням на CDN працюють з
wire:navigate, але залежать від того, що віддає кеш - варто перевірити, що кешований HTML не містить персональних даних.
Параметри дії передаються як аргументи виклику:
<button wire:click="addToCart({{ $product->id }}, 2)">У кошик</button>
<button wire:click="setStatus('archived')">В архів</button>
public function addToCart(int $productId, int $qty = 1): void
{
$product = Product::query()->published()->findOrFail($productId);
$this->cart->add($product, min($qty, 10));
}
Моделі й енуми як параметри. Якщо параметр типізований моделлю, Livewire знайде її за переданим ключем (як прив'язка моделей у маршрутах):
public function delete(Post $post): void
{
$this->authorize('delete', $post);
$post->delete();
}
Параметри - це введення користувача. У консолі браузера можна викликати $wire.addToCart(999, 100000). Тип перевіряє PHP, а межі, існування запису й права - лише ваш код.
Не тільки клік. Після wire: можна вказати будь-яку подію браузера:
<input wire:keydown.enter="search">
<input wire:keydown.escape="clear">
<div wire:mouseenter="loadPreview({{ $id }})">
<trix-editor wire:trix-change="setContent($event.target.value)">
Модифікатори (працюють через x-on Alpine):
| Модифікатор | Що робить |
|---|---|
.prevent |
preventDefault() |
.stop |
stopPropagation() |
.once |
спрацювати один раз |
.self |
лише якщо подія виникла на самому елементі |
.window, .document |
слухати на window / document |
.outside |
клік поза елементом |
.debounce.300ms, .throttle.500ms |
обмеження частоти |
.shift.enter, .ctrl, .escape |
клавіші й комбінації |
Модифікатори Livewire 4:
.async- виконати дію паралельно, не чекаючи черги;.renderless- без повторного рендеру;.preserve-scroll- зберегти позицію прокрутки після оновлення.
$event - об'єкт події браузера в виразі: wire:change="setColor($event.target.value)".
Магічні дії без методу: $refresh (перерендерити), $set('tab', 'settings') (встановити властивість), $toggle('open'), $dispatch(...), $parent.method().
Стан завантаження: елемент, що ініціював запит, автоматично отримує атрибут data-loading - кнопку легко стилізувати через Tailwind (data-loading:opacity-50).
Обидва способи показують чи ховають частину інтерфейсу, але працюють на різних рівнях.
@if - на сервері. Blade вирішує, чи потрапить розмітка в HTML взагалі:
@if ($showModal)
<div class="modal">...</div>
@endif
Щоб показати вікно, потрібен запит на сервер: змінити $showModal, перерендерити компонент, отримати новий HTML.
wire:show - у браузері. Елемент завжди є в DOM, а директива перемикає display: none за виразом:
<button type="button" x-on:click="$wire.showModal = true">Новий пост</button>
<div wire:show="showModal" class="modal">
<textarea wire:model="content"></textarea>
<button wire:click="save">Опублікувати</button>
</div>
Відкриття вікна - без запиту: $wire.showModal = true змінює значення в браузері, і wire:show реагує одразу. На сервер значення піде з наступною дією. Після save() на сервері можна виставити $this->showModal = false - і вікно закриється.
Коли wire:show:
- миттєве перемикання (модальні вікна, вкладки, розгортання деталей), де затримка запиту помітна;
- вміст дешевий і може бути в HTML завжди;
- потрібні анімації появи -
wire:showпоєднується зx-transitionAlpine, бо лише перемикаєdisplay.
Коли @if:
- вміст не повинен потрапити в HTML - дані, доступні лише певній ролі. Прихований
wire:showелемент усе одно є в коді сторінки й видний у DevTools; - вміст дорогий для рендеру (запити до бази в шаблоні) і потрібен рідко;
- великі блоки, які збільшують розмір відповіді на кожному рендері.
Пов'язані директиви:
wire:text="expression"- вставити текст виразу без запиту (лічильник символів, попередній перегляд);wire:cloak- сховати елемент до ініціалізації Livewire, щоб не «блимали» елементи, які мали бути приховані;- Alpine
x-show- те саме для стану, що живе лише в Alpine, без Livewire.
Пастка безпеки: wire:show - це про вигляд, а не про доступ. Кнопка «Видалити», прихована через wire:show, усе одно викликає метод, який можна викликати й без кнопки, - перевірка прав потрібна в методі.