Posisi Livewire

Komponen reaktif yang keadaannya tersimpan di sisi server Laravel.

Daftar isi

Livewire adalah framework untuk membangun komponen halaman yang reaktif dengan logika yang tetap berjalan di server Laravel. Topik ini membahas model kerjanya, membangun satu komponen pencarian catatan pada aplikasi catatan, dan menunjukkan siklus permintaannya melalui keluaran nyata. Setelah membacanya, keputusan menggunakan atau tidak menggunakan Livewire untuk sebuah halaman dapat diambil dengan alasan teknis yang jelas.

Model kerja Livewire#

Sebuah komponen Livewire terdiri atas kelas PHP dan template Blade. Saat halaman pertama kali dibuka, Laravel merender template itu menjadi HTML utuh, persis seperti halaman Blade biasa. Halaman tetap lengkap meskipun JavaScript belum berjalan, sehingga konten awal tampil cepat dan mudah dibaca mesin pencari.

Perilaku dinamis muncul setelah skrip livewire.js dimuat. Pada template, atribut wire:model mengaitkan elemen masukan dengan properti publik kelas, sedangkan atribut wire:click memanggil metode kelas. Ketika pengguna mengetik atau menekan tombol, livewire.js mengirim permintaan ke endpoint pembaruan milik Livewire dengan membawa snapshot, yaitu data komponen yang diserialisasi beserta checksum pengamannya. Server membangun ulang kelas tersebut, memperbarui properti publiknya, menjalankan metode yang diminta, lalu merender ulang template menjadi HTML baru. Browser menerima HTML itu dan mengubah DOM lama menjadi DOM baru secara selektif, mekanisme yang disebut morphing, tanpa memuat ulang halaman.

Dengan model ini, seluruh keadaan komponen tersimpan di server. Browser hanya mengirim snapshot kecil dan menerima HTML, sehingga logika, validasi, dan akses database tidak pernah diturunkan ke sisi klien. Alur satu interaksi dirangkum pada diagram berikut.

Browserinput wire:model.livediubah penggunalivewire.jskirim snapshot,nilai baru, aksiServer Laravelbangun ulang kelas,jalankan render()BrowserHTML barudisulap (morphing)
Alur satu interaksi komponen Livewire

Komponen pencarian catatan#

Livewire dipasang sebagai paket Composer, kemudian komponen dibuat dengan perintah Artisan. Kedua perintah berikut memerlukan koneksi jaringan dan menghasilkan berkas baru, sehingga tidak dieksekusi ulang di sini.

bash
# tidak-dijalankan
# menginstal livewire ke proyek Laravel
cd ~/aplikasi-catatan
composer require livewire/livewire

Keluaran sungguhan dari satu percobaan pemasangan pada salinan proyek berakhir dengan baris berikut.

text Hasil perintah composer require livewire/livewire
82 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
> @php artisan vendor:publish --tag=laravel-assets --ansi --force

  INFO  No publishable resources for tag [laravel-assets].  

No security vulnerability advisories found.
Using version ^4.4 for livewire/livewire

Livewire 4 menyediakan dua bentuk komponen. Bentuk default adalah single-file component, yaitu satu berkas Blade berisi kelas anonim dan markahnya dengan nama seperti resources/views/components/⚡pencari-catatan.blade.php, sedangkan opsi --class menghasilkan kelas dan view yang terpisah. Bentuk kelas digunakan pada topik ini agar setiap berkas dapat dibahas tersendiri.

bash
# tidak-dijalankan
# membuat komponen livewire berbasis kelas
cd ~/aplikasi-catatan
php artisan make:livewire PencariCatatan --class
text Hasil perintah make:livewire PencariCatatan --class
   INFO  Livewire component [app/Livewire/PencariCatatan.php] created successfully.

Komponen yang dibahas adalah pencari catatan, yaitu kotak pencarian yang menyaring daftar catatan mengikuti kata kunci. Kelasnya dibuat pada berkas app/Livewire/PencariCatatan.php dengan isi berikut.

php app/Livewire/PencariCatatan.php
// tidak-dijalankan
<?php

namespace App\Livewire;

use App\Models\Note;
use Livewire\Component;

class PencariCatatan extends Component
{
    public string $cari = '';

    public function render()
    {
        return view('livewire.pencari-catatan', [
            'catatan' => Note::with('category')
                ->where('judul', 'like', "%{$this->cari}%")
                ->orderBy('judul')
                ->get(),
        ]);
    }
}

Properti publik $cari menyimpan kata kunci pencarian. Sifat publik bukan pilihan gaya, melainkan kebutuhan mekanisme: hanya properti publik yang diserialisasi ke snapshot dan yang dapat diikat dari template. Metode render() menjalankan query model Note seperti pada controller biasa, termasuk pemuatan relasi kategori dengan with(). Templatenya dibuat pada berkas resources/views/livewire/pencari-catatan.blade.php.

blade pencari-catatan.blade.php
<div>
    <input type="search" placeholder="Cari judul catatan" wire:model.live="cari">

    <p>{{ $catatan->count() }} catatan ditemukan.</p>

    <ul>
        @forelse ($catatan as $note)
            <li wire:key="catatan-{{ $note->id }}">
                {{ $note->judul }}
                @if ($note->category)
                    &mdash; {{ $note->category->nama }}
                @endif
            </li>
        @empty
            <li>Tidak ada catatan yang cocok.</li>
        @endforelse
    </ul>
</div>

Atribut wire:model.live mengikat kotak masukan ke $cari dan mengirim pembaruan ke server setiap kali isi masukan berubah. Atribut wire:key memberi identitas tetap pada setiap butir daftar sehingga proses morphing mencocokkan butir berdasarkan kuncinya, bukan berdasarkan posisinya. Komponen ini juga dapat dijadikan halaman penuh: kelasnya dipasang langsung sebagai aksi rute, dan Livewire merender hasilnya ke dalam sebuah layout.

php Pendaftaran komponen halaman penuh pada routes/web.php
// tidak-dijalankan
use App\Livewire\PencariCatatan;

Route::get('/cari', PencariCatatan::class);

Layout untuk komponen halaman penuh dibuat dengan php artisan livewire:layout, yang menghasilkan resources/views/layouts/app.blade.php berisi pemanggilan {{ $slot }} serta @livewireScripts. Rute yang terdaftar dapat diperiksa dengan perintah berikut.

bash
# tidak-dijalankan
# memeriksa rute komponen yang sudah terdaftar
cd ~/aplikasi-catatan
php artisan route:list --path=cari

Keluaran sungguhan dari salinan proyek menunjukkan rutenya.

text Hasil perintah route:list --path=cari
  GET|HEAD       cari ............................ App\Livewire\PencariCatatan

                                                            Showing [1] routes

Siklus permintaan yang tampak di HTML#

HTML awal komponen dapat dirender tanpa browser melalui Tinker. Perintah berikut memanggil Livewire::mount(), yaitu fungsi yang sama yang digunakan framework saat halaman dimuat.

bash
# tidak-dijalankan
# merender HTML awal komponen tanpa browser
cd ~/aplikasi-catatan
php artisan tinker --execute='
echo \Livewire\Livewire::mount("pencari-catatan");
'

Keluaran sungguhan dari salinan proyek dengan tiga catatan seed adalah berikut.

text Hasil render awal komponen pencari-catatan
<div wire:snapshot="{&quot;data&quot;:{&quot;cari&quot;:&quot;&quot;},&quot;memo&quot;:{&quot;id&quot;:&quot;7wFkBrikjqPYo764Fek2&quot;,&quot;name&quot;:&quot;pencari-catatan&quot;,&quot;path&quot;:&quot;\/&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;release&quot;:&quot;a-a-a&quot;,&quot;children&quot;:[],&quot;scripts&quot;:[],&quot;assets&quot;:[],&quot;errors&quot;:[],&quot;locale&quot;:&quot;en&quot;,&quot;islands&quot;:[]},&quot;checksum&quot;:&quot;618673ad99adb18c3d363dad6489065294d4cc8364079952f7a2d483cee17b27&quot;}" wire:effects="[]" wire:id="7wFkBrikjqPYo764Fek2" wire:name="pencari-catatan">
    <input type="search" placeholder="Cari judul catatan" wire:model.live="cari">

    <p>3 catatan ditemukan.</p>

    <ul>
        <!--[if BLOCK]><![endif]-->            <li wire:key="catatan-2">
                Belajar Laravel Livewire
                <!--[if BLOCK]><![endif]-->                    &mdash; Belajar
                <!--[if ENDBLOCK]><![endif]-->            </li>
                    <li wire:key="catatan-3">
                Daftar belanja mingguan
                <!--[if BLOCK]><![endif]-->                    &mdash; Pribadi
                <!--[if ENDBLOCK]><![endif]-->            </li>
                    <li wire:key="catatan-1">
                Rapat koordinasi tim
                <!--[if BLOCK]><![endif]-->                    &mdash; Kerja
                <!--[if ENDBLOCK]><![endif]-->            </li>
        <!--[if ENDBLOCK]><![endif]-->    </ul>
</div>

Bagian penting terletak pada atribut pembuka. wire:snapshot membawa data komponen, di sini cari bernilai kosong, beserta identitas dan checksum-nya. checksum mencegah snapshot dimanipulasi dari sisi browser, sedangkan wire:id menandai instance komponen pada halaman itu. Bagian bawahnya adalah HTML biasa hasil render Blade. Nilai wire:id dan checksum berubah pada setiap render, sehingga keluaran persisnya berbeda dari contoh ini.

Interaksi dapat diuji tanpa browser menggunakan Livewire::test(), yang mengeksekusi komponen di dalam proses PHP.

bash
# tidak-dijalankan
# menguji penyaringan komponen tanpa browser
cd ~/aplikasi-catatan
php artisan tinker --execute='
$hasil = \Livewire\Livewire::test(\App\Livewire\PencariCatatan::class);
$hasil->assertSee("Rapat koordinasi tim");
$hasil->set("cari", "bel");
$hasil->assertSee("Belajar Laravel Livewire");
$hasil->assertSee("Daftar belanja mingguan");
$hasil->assertDontSee("Rapat koordinasi tim");
echo "Tiga pernyataan lolos: daftar tersaring mengikuti kata kunci." . PHP_EOL;
'

Pemanggilan set("cari", "bel") meniru ketikan pengguna, lalu setiap pernyataan memeriksa HTML hasil render ulang di server. Keluaran percobaan pada salinan proyek adalah berikut.

text Hasil pengujian Livewire::test
Tiga pernyataan lolos: daftar tersaring mengikuti kata kunci.

Kapan Livewire cocok digunakan#

Livewire menawarkan titik tengah antara Blade murni dan aplikasi SPA. Rute, validasi, dan query tetap berada di kode PHP yang sama, tanpa API terpisah, sedangkan bagian dinamisnya cukup ditulis sebagai kelas dan template. Konsekuensinya ada pada jaringan: setiap interaksi menimbulkan permintaan ke server, sehingga halaman dengan animasi kaya khas SPA lebih tepat ditangani pendekatan Inertia.

Kebutuhan halamanPendekatan yang lazim
Halaman informatif dengan interaksi minimBlade biasa tanpa komponen dinamis
Formulir, pencarian, filter, dan daftar yang merespons masukanLivewire
Antarmuka bergaya SPA dengan pustaka Vue atau ReactInertia

Kesalahan umum#

Kesalahan pertama adalah menggunakan komponen sebagai halaman penuh tanpa layout. Rutenya terdaftar, tetapi halaman gagal dengan error No hint path defined for [layouts]. karena Livewire mencari layout bawaan bernama layouts::app. Perbaikannya adalah menjalankan php artisan livewire:layout sekali, lalu menyesuaikan layout yang dihasilkan.

Kesalahan kedua adalah menempatkan koleksi besar pada properti publik, misalnya public $catatan; yang diisi seluruh isi tabel. Koleksi itu ikut diserialisasi ke snapshot dan dikirim bolak-balik pada setiap interaksi, sehingga payload membengkak tanpa manfaat. Properti publik cukup menyimpan kata kunci atau pengenal, sedangkan koleksi diambil dari database di dalam render().

Kesalahan ketiga adalah melupakan wire:key pada perulangan. Tanpa kunci, morphing mencocokkan butir berdasarkan posisi, sehingga daftar yang urutannya berubah dapat membuat isi DOM tertukar, misalnya nilai masukan yang sedang diisi pindah ke baris lain. Beri kunci berisi pengenal unik, seperti wire:key="catatan-{{ $note->id }}".

Ringkasan#

  • Komponen Livewire adalah kelas PHP beserta template Blade yang dirender penuh oleh server pada muat awal, kemudian dibangun ulang oleh server untuk setiap interaksi yang dikirim livewire.js.
  • Setiap interaksi mengirim snapshot berisi properti publik dan checksum ke endpoint pembaruan, lalu browser mengganti DOM secara selektif dengan HTML hasil render ulang di server.
  • Hanya properti publik yang ikut snapshot, sehingga data besar sebaiknya diambil dari database di dalam render() dan tidak disimpan sebagai properti.
  • Livewire::test() dan Livewire::mount() memungkinkan perilaku komponen diuji dan diperiksa langsung dari Tinker tanpa browser.

Referensi#