Eventy i komunikacja między komponentami w Livewire

Jedną z najciekawszych i najważniejszych funkcjonalności Livewire jest mechanizm zdarzeń (events), który umożliwia komunikację między różnymi komponentami. Dzięki temu możesz stworzyć złożoną architekturę aplikacji, w której poszczególne elementy (komponenty) przekazują sobie dane i reagują na zmiany w czasie rzeczywistym, bez konieczności pisania dużych ilości kodu JavaScript.

W tym artykule przyjrzymy się, jak działa mechanizm zdarzeń w Livewire, omówimy metody takie jak emit() i emitTo(), poznamy listeners, a także zobaczymy, jak komponować aplikację w oparciu o wiele komponentów Livewire. Artykuł jest kontynuacją poprzednich tematów:

Rola zdarzeń w Livewire

W Livewire możesz wysyłać zdarzenia (emit, emitTo, dispatchBrowserEvent) z jednego komponentu do innego lub do przeglądarki. Dzięki temu możesz:

  • Zaktualizować inny komponent po wykonaniu określonej akcji (np. po dodaniu zadania w jednym komponencie, odświeżyć listę w drugim komponencie).
  • Komunikować się między komponentami rodzic-dziecko.
  • Tworzyć architekturę, w której wiele różnych komponentów reaguje na to samo zdarzenie.
  • Wywoływać zdarzenia w przeglądarce, np. do wyświetlenia natywnych powiadomień (alertów).

Metody emit() i emitTo()

  • emit('nazwaZdarzenia', $parametry...) – pozwala wyemitować zdarzenie o podanej nazwie. Każdy komponent, który ma w $listeners zdefiniowaną reakcję na to zdarzenie, może na nie zareagować.
  • emitTo('nazwaKomponentu', 'nazwaZdarzenia', $parametry...) – pozwala skierować zdarzenie bezpośrednio do konkretnego komponentu Livewire (zdefiniowanego nazwą klasy lub aliasem komponentu).

$listeners

W komponencie, który ma reagować na zdarzenia, definiujemy tablicę $listeners, która określa listę „obsługiwanych” nazw zdarzeń oraz metod, jakie mają być wywołane:

				
					protected $listeners = [
    'zadanieUtworzone' => 'odswiezListeZadan'
];

				
			

W tym przykładzie, gdy dany komponent otrzyma zdarzenie o nazwie zadanieUtworzone, wywoła metodę odswiezListeZadan.

Komunikacja między rodzicem a dziećmi

W typowym scenariuszu, gdy mamy komponent rodzica i dzieci, można wywoływać metody dzieci poprzez:

  • emitTo(), gdzie jako nazwę komponentu podajemy unikalną nazwę wystąpienia lub alias.
  • Bezpośrednie przekazanie danych do widoku rodzica, jeśli logika nie wymaga stricte zdarzeń.
  • Możliwe jest również „bubble’owanie” zdarzeń do góry (od dziecka do rodzica), jeśli komponent dziecko użyje emitUp('nazwaZdarzenia').

Porady dotyczące projektowania struktury komponentów

  1. Podział na mniejsze komponenty – zamiast jednego dużego komponentu, warto dzielić aplikację na mniejsze. Każdy komponent może mieć konkretną odpowiedzialność (np. formularz do dodania zadania, lista zadań).
  2. Jasny podział ról – jeśli jeden komponent służy do dodawania rekordów (formularz), a drugi do ich wyświetlania, możesz łatwo zarządzać stanem poprzez emitowanie zdarzeń.
  3. Rozważaj emitowanie zdarzeń tylko w momentach kluczowych – np. gdy dane zostaną zapisane w bazie, a nie przy każdej drobnej zmianie, by uniknąć nadmiernej liczby interakcji.

Ćwiczenia

W ramach ćwiczeń zbudujmy dwa komponenty:

  1. TaskCreate – komponent odpowiedzialny za dodawanie nowego zadania.
  2. TaskList – komponent odpowiedzialny za wyświetlanie listy zadań i ewentualne dodatkowe operacje na nich (CRUD).

Ćwiczenie 1. Komponent TaskCreate

Generowanie komponentu

Wykorzystujemy Artisan:

				
					php artisan make:livewire TaskCreate

				
			

Implementacja logiki dodawania zadania

Załóżmy, że mamy model Task z polami name oraz status. Komponent TaskCreate będzie zawierał formularz do dodania nowego zadania:

				
					<?php

namespace App\Http\Livewire;

use App\Models\Task;
use Livewire\Component;

class TaskCreate extends Component
{
    public $name;
    public $status;

    protected $rules = [
        'name'   => 'required|min:3',
        'status' => 'required',
    ];

    public function render()
    {
        return view('livewire.task-create');
    }

    public function createTask()
    {
        // Walidacja danych
        $this->validate();

        // Zapis do bazy
        Task::create([
            'name'   => $this->name,
            'status' => $this->status,
        ]);

        // Resetowanie pól formularza
        $this->reset(['name', 'status']);

        // Wyemitowanie zdarzenia do innych komponentów
        $this->emit('zadanieUtworzone');

        // (Opcjonalnie) Wysłanie zdarzenia do przeglądarki
        $this->dispatchBrowserEvent('zadanie-utworzone', [
            'message' => 'Nowe zadanie zostało pomyślnie dodane!'
        ]);
    }
}

				
			

Widok task-create.blade.php

				
					<div>
    <h2>Dodaj nowe zadanie</h2>

    <form wire:submit.prevent="createTask">
        <div>
            <label for="name">Nazwa zadania</label>
            <input type="text" id="name" wire:model="name">
            @error('name') <span class="error">{{ $message }}</span> @enderror
        </div>

        <div>
            <label for="status">Status</label>
            <input type="text" id="status" wire:model="status">
            @error('status') <span class="error">{{ $message }}</span> @enderror
        </div>

        <button type="submit">Dodaj zadanie</button>
    </form>
</div>

				
			

W momencie kliknięcia przycisku „Dodaj zadanie” Livewire wywołuje metodę createTask(). Po pomyślnym dodaniu zadania do bazy następuje emit('zadanieUtworzone'), które może przechwycić inny komponent – w naszym przypadku będzie to TaskList.

Ćwiczenie 2: Komponent TaskList

Generowanie komponentu

				
					php artisan make:livewire TaskList

				
			

Implementacja logiki wyświetlania i obsługa eventu

W komponencie TaskList odbieramy zdarzenie zadanieUtworzone i odświeżamy listę zadań:

				
					<?php

namespace App\Http\Livewire;

use App\Models\Task;
use Livewire\Component;
use Livewire\WithPagination;

class TaskList extends Component
{
    use WithPagination;

    // Definiujemy listeners
    protected $listeners = [
        'zadanieUtworzone' => 'odswiezListeZadan'
    ];

    public function render()
    {
        return view('livewire.task-list', [
            'tasks' => $this->pobierzListeZadan()
        ]);
    }

    // Metoda wywoływana po otrzymaniu eventu
    public function odswiezListeZadan()
    {
        // Możemy np. zresetować paginację, by wrócić na pierwszą stronę
        $this->resetPage();
    }

    protected function pobierzListeZadan()
    {
        return Task::orderBy('id', 'desc')->paginate(10);
    }
}

				
			

Zwróć uwagę, że w $listeners definiujemy parę klucz-wartość, gdzie kluczem jest nazwa zdarzenia, a wartością nazwa metody, która ma zostać wywołana po nadejściu zdarzenia. W tym przypadku jest to odswiezListeZadan.

Widok task-list.blade.php

				
					<div>
    <h2>Lista zadań</h2>

    <ul>
        @foreach($tasks as $task)
            <li>
                {{ $task->name }} - {{ $task->status }}
            </li>
        @endforeach
    </ul>

    
    {{ $tasks->links() }}
</div>

				
			

Teraz, gdy w komponencie TaskCreate dodamy nowe zadanie, zdarzenie zadanieUtworzone zostanie wyemitowane i TaskList odbierze je, po czym wywoła metodę odswiezListeZadan(). Ta metoda może zresetować paginację i w efekcie spowoduje ponowne wyrenderowanie listy w widoku.

Ćwiczenie 3: (Opcjonalnie) Notyfikacje w przeglądarce

W kodzie TaskCreate widzimy przykład użycia dispatchBrowserEvent():

				
					$this->dispatchBrowserEvent('zadanie-utworzone', [
    'message' => 'Nowe zadanie zostało pomyślnie dodane!'
]);

				
			

W widoku Blade możemy nasłuchiwać na takie zdarzenie za pomocą JS. Na przykład, jeśli używamy Alpine.js, możemy umieścić coś takiego:

				
					<div x-data
     x-on:zadanie-utworzone.window="alert($event.detail.message)">
    ...
</div>

				
			

Po tym wydarzeniu w przeglądarce zostanie wyświetlone powiadomienie typu alert z treścią: „Nowe zadanie zostało pomyślnie dodane!”.

Podsumowanie

Mechanizm zdarzeń w Livewire jest kluczowy dla tworzenia bardziej rozbudowanych aplikacji, w których wiele komponentów musi ze sobą współpracować. Główne punkty, na które warto zwrócić uwagę:

  1. Eventy – emit() oraz emitTo() to proste, a jednocześnie bardzo potężne narzędzia do komunikacji pomiędzy komponentami.
  2. Listeners – $listeners w komponencie pozwala na reakcję na określone zdarzenia – wystarczy przypisać nazwę zdarzenia do metody.
  3. Struktura aplikacji – warto dzielić aplikację na mniejsze komponenty (np. TaskCreate, TaskList), a następnie łączyć je za pomocą zdarzeń.
  4. dispatchBrowserEvent – umożliwia komunikację z samą przeglądarką, co pozwala na wykorzystanie natywnych funkcjonalności, takich jak alert() czy animacje w JS/Alpine.js.

Dzięki eventom możemy łatwo tworzyć dynamiczne interfejsy – np. po dodaniu zadania w jednym komponencie, inny odświeży się automatycznie i pokaże nowe dane. To kolejny krok w kierunku budowania złożonych, a jednocześnie intuicyjnych aplikacji w ekosystemie Laravel + Livewire.