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

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>

Що відбувається при кліку:

  1. Livewire перехоплює клік і запитує нову сторінку звичайним GET-запитом;
  2. замінює вміст <body> і оновлює <head> (заголовок, мета-теги);
  3. змінює адресу через History API - кнопки «Назад» і «Вперед» працюють;
  4. показує смугу прогресу вгорі сторінки.

Чому швидше: 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).

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

Обидва способи показують чи ховають частину інтерфейсу, але працюють на різних рівнях.

@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-transition Alpine, бо лише перемикає display.

Коли @if:

  • вміст не повинен потрапити в HTML - дані, доступні лише певній ролі. Прихований wire:show елемент усе одно є в коді сторінки й видний у DevTools;
  • вміст дорогий для рендеру (запити до бази в шаблоні) і потрібен рідко;
  • великі блоки, які збільшують розмір відповіді на кожному рендері.

Пов'язані директиви:

  • wire:text="expression" - вставити текст виразу без запиту (лічильник символів, попередній перегляд);
  • wire:cloak - сховати елемент до ініціалізації Livewire, щоб не «блимали» елементи, які мали бути приховані;
  • Alpine x-show - те саме для стану, що живе лише в Alpine, без Livewire.

Пастка безпеки: wire:show - це про вигляд, а не про доступ. Кнопка «Видалити», прихована через wire:show, усе одно викликає метод, який можна викликати й без кнопки, - перевірка прав потрібна в методі.

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