Topik model LiveView menempatkan halaman sebagai process berkondisi, dan topik ini merinci dua callback yang menggerakkannya sehari-hari, yaitu mount/3 pada masa awal hidup dan handle_event/3 pada masa interaksi. Pembahasan mencakup param (params) dan session yang masuk lewat mount, callback handle_params/3 untuk param URL, siklus hidup lengkap dari render pertama sampai event loop, serta satu skrip utuh yang menjalankan semuanya berurutan. Hasil akhirnya adalah kemampuan melacak kapan setiap callback dipanggil dan apa yang boleh dilakukannya.
Siklus hidup satu koneksi#
Siklus hidup sebuah LiveView bergerak melalui tahap yang urutannya tetap. Browser meminta halaman, server merender HTML pertama tanpa process, lalu browser membuka WebSocket, process LiveView lahir, dan barulah event mengalir dua arah. Tabel berikut merangkum tahap itu beserta callback yang mengerjakannya:
| Tahap | Callback | Keterangan |
|---|---|---|
| Render pertama (dead render) | mount/3 dan render/1 | HTML awal dikirim bersama respons halaman |
| Koneksi WebSocket | connected?/1 bernilai true | process LiveView lahir di server |
| Setelah koneksi | mount/3 dijalankan lagi, handle_params/3 dipanggil | langganan PubSub sebaiknya dilakukan di sini |
| Event dari browser | handle_event/3 | satu callback per satu event |
| Pesan dari process lain | handle_info/2 | diteruskan dari mailbox process |
Pola penting pada tabel itu adalah mount/3 dijalankan dua kali, sekali untuk dead render dan sekali ketika koneksi terjalin. Pemanggilan kedua itulah tempat pekerjaan yang hanya boleh dilakukan oleh process yang hidup, misalnya langganan PubSub, dan kedua pemanggilan itu dibedakan lewat Phoenix.LiveView.connected?(socket) yang bernilai false lalu true.
mount/3: param dan session#
mount/3 menerima tiga argumen, yaitu param URL, session, dan socket. Session membawa data yang ditanam server saat halaman dirender, misalnya identitas pengguna yang sudah terautentikasi, sedangkan param URL berisi data rute. Keduanya hanya dibaca sekali di mount, lalu apa pun yang akan digunakan sepanjang hidup halaman disimpan ke assigns agar tidak hilang.
Pada skrip utuh topik ini, mount/3 membaca kunci "siswa" dari session dan mengisinya ke assigns nama. Kunci yang tidak ada ditangani lewat Map.get/3 dengan nilai default, karena session mungkin saja tidak memuat apa yang diharapkan. Pola ini menjaga mount tetap total, yaitu selalu menghasilkan assigns yang lengkap untuk semua kemungkinan masukan.
handle_params/3 untuk param URL#
Callback handle_params/3 menerima param URL, URI, dan socket. Kerangka memanggilnya setiap kali param halaman berubah, misalnya ketika URL dipatch tanpa memuat ulang halaman. Callback ini paling tepat digunakan untuk membaca angka atau slug dari URL, lalu menyetel assigns yang bergantung padanya. Nilai balikannya berupa {:noreply, socket} atau {:noreply, socket, ...} dengan opsi tambahan, dan setiap assigns yang diubah di callback ini ikut memicu render.
Pada skrip, handle_params/3 membaca param "awal" dari URL dan mengisinya ke assigns hitung sebagai nilai mula. Param datang sebagai map dengan kunci string, sehingga "10" perlu dikonversi menjadi bilangan sebelum digunakan dalam perhitungan. Perlakuan yang sama berlaku pada param form di callback berikutnya.
handle_event/3: event loop#
Setiap interaksi pengguna yang ditandai atribut phx-click, phx-submit, atau phx-change menjadi sebuah event yang tiba di handle_event/3. Callback ini menerima nama event, param event, dan socket, lalu menentukan tindakannya dengan pencocokan pola pada nama event dan param sekaligus. Bentuk handle_event("atur", %{"nilai" => nilai}, socket) hanya dipanggil untuk event bernama atur yang paramnya memuat kunci "nilai", sehingga bentuk param tiap event terbaca langsung dari head klausa. Nilai balikannya wajib berupa {:noreply, socket} dengan assigns yang sudah diperbarui.
Skrip berikut menjalankan seluruh siklus secara berurutan, yaitu mount dengan session, handle_params/3 dengan param URL, dua event tambah berturut-turut, dan satu event atur yang membawa param form:
# siklus.exs — siklus hidup LiveView: mount, handle_params, dan handle_event berurutan
Mix.install([{:phoenix_live_view, "~> 1.1"}, {:jason, "~> 1.4"}])
Logger.configure(level: :warning)
defmodule NilaiLive do
use Phoenix.LiveView
def mount(_params, session, socket) do
nama = Map.get(session, "siswa", "tanpa nama")
{:ok, assign(socket, nama: nama, hitung: 0)}
end
def handle_params(params, _uri, socket) do
awal = params |> Map.get("awal", "0") |> String.to_integer()
{:noreply, assign(socket, :hitung, awal)}
end
def handle_event("tambah", _params, socket) do
{:noreply, update(socket, :hitung, &(&1 + 1))}
end
def handle_event("atur", %{"nilai" => nilai}, socket) do
{:noreply, assign(socket, :hitung, String.to_integer(nilai))}
end
def render(assigns) do
~H"<p>Siswa {@nama}: hitung {@hitung}</p>"
end
end
Code.ensure_loaded(Phoenix.LiveViewTest)
# tahap 1: mount menerima session dan menyiapkan assigns awal
{:ok, socket} = NilaiLive.mount(%{}, %{"siswa" => "Citra"}, struct(Phoenix.LiveView.Socket))
IO.puts(Phoenix.LiveViewTest.rendered_to_string(NilaiLive.render(socket.assigns)))
# tahap 2: handle_params membaca param URL sesudah koneksi terjalin
{:noreply, socket2} = NilaiLive.handle_params(%{"awal" => "10"}, "/", socket)
IO.puts(Phoenix.LiveViewTest.rendered_to_string(NilaiLive.render(socket2.assigns)))
# tahap 3: dua event "tambah" diproses satu per satu oleh loop yang sama
{:noreply, socket3} = NilaiLive.handle_event("tambah", %{}, socket2)
{:noreply, socket4} = NilaiLive.handle_event("tambah", %{}, socket3)
IO.puts(Phoenix.LiveViewTest.rendered_to_string(NilaiLive.render(socket4.assigns)))
# tahap 4: event "atur" membawa param form yang dicocokkan polanya
{:noreply, socket5} = NilaiLive.handle_event("atur", %{"nilai" => "24"}, socket4)
IO.puts(Phoenix.LiveViewTest.rendered_to_string(NilaiLive.render(socket5.assigns)))<p>Siswa Citra: hitung 0</p>
<p>Siswa Citra: hitung 10</p>
<p>Siswa Citra: hitung 12</p>
<p>Siswa Citra: hitung 24</p>Empat baris keluaran memetakan empat tahap skrip. Baris pertama berasal dari assigns hasil mount dengan hitung bernilai 0, sedangkan baris kedua menunjukkan bahwa handle_params/3 menaikkan nilai itu menjadi 10 berdasarkan param URL. Dua event tambah memindahkan nilai ke 11 lalu 12 pada satu loop yang sama, dan baris terakhir membuktikan bahwa klausa atur membaca kunci "nilai" dari param form lalu menyetel ulang nilai menjadi 24. Skripnya dapat dijalankan kembali dari terminal:
# menjalankan skrip siklus dari folder topik
elixir siklus.exs<p>Siswa Citra: hitung 0</p>
<p>Siswa Citra: hitung 10</p>
<p>Siswa Citra: hitung 12</p>
<p>Siswa Citra: hitung 24</p>Perhatikan bahwa setiap callback mengembalikan socket baru dan socket itu diteruskan ke pemanggilan berikutnya. Rantai socket, socket2, sampai socket5 pada skrip adalah bayangan langsung dari loop GenServer yang membawa state terbaru, hanya saja di sini setiap putaran loopnya terlihat jelas sebagai satu pemanggilan fungsi.
Kesalahan umum#
Kesalahan pertama adalah meletakkan langganan atau pekerjaan berat di mount/3 tanpa gerbang connected?/1. Pada dead render, process belum hidup dan belum terhubung ke browser, sehingga langganan yang dibuat saat itu diterima process yang segera dibuang. Pekerjaan yang memiliki efek samping, misalnya langganan PubSub atau pembacaan basis data, sebaiknya dipindahkan ke dalam cabang if connected?(socket) agar hanya berjalan sekali pada mount kedua.
Kesalahan kedua adalah menjalankan pekerjaan yang berlangsung lama di handle_event/3. Callback ini berjalan di process halaman, sehingga event berikutnya tertahan sampai pekerjaan selesai, dan halaman terasa tidak merespons bagi penggunanya. Pekerjaan yang berlangsung lama, misalnya panggilan jaringan atau pengolahan berkas, sebaiknya dijalankan lewat Task, dan hasilnya dikirim kembali sebagai pesan yang ditangani handle_info/2.
Kesalahan ketiga adalah lupa mengembalikan socket yang sudah diperbarui. Klausa yang mengembalikan socket lama tanpa assign membuat event tampak diproses tetapi tidak mengubah apa pun di browser, karena render untuk assigns yang tidak berubah tidak menghasilkan patch. Bentuk {:noreply, assign(socket, ...)} adalah pola yang harus dipertahankan.
Ringkasan#
- Siklus hidup LiveView bergerak dari dead render dengan
mount/3danrender/1, koneksi WebSocket,handle_params/3, lalu event loop yang berisihandle_event/3danhandle_info/2. mount/3menerima param dan session sekali di awal, sedangkanhandle_params/3menangani param URL dan dipanggil ulang setiap perubahan URL.handle_event/3memilih tindakan dengan pencocokan pola pada nama event dan param, dan selalu mengembalikan{:noreply, socket}dengan assigns terbaru.- Gerbang
connected?/1membedakan mount pertama dari mount kedua, dan pekerjaan yang hanya sah untuk process yang hidup diletakkan di cabangnya.
Referensi#
- Modul Phoenix.LiveView — Hexdocs Phoenix LiveView
— dokumentasi
mount/3,handle_params/3,handle_event/3,handle_info/2, danconnected?/1. - Bindings — Hexdocs Phoenix LiveView
— rincian binding
phx-click,phx-change, danphx-submityang memicu event. - Form bindings — Hexdocs Phoenix LiveView
— bentuk param yang diteruskan ke
handle_event/3oleh elemen form.