Phoenix LiveView 理念
LiveView 将状态保存在服务端,仅通过 WebSocket 发送 HTML 差异。无需 JSON API,无需客户端状态管理。

基础计数器 LiveView
defmodule MyAppWeb.CounterLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0)}
end
def render(assigns) do
~H"""
<div>
<h1>Count: <%= @count %></h1>
<button phx-click="inc">+</button>
<button phx-click="dec">-</button>
</div>
"""
end
def handle_event("inc", _params, socket) do
{:noreply, update(socket, :count, &(&1 + 1))}
end
def handle_event("dec", _params, socket) do
{:noreply, update(socket, :count, &(&1 - 1))}
end
end
使用 PubSub 实现实时聊天
defmodule MyAppWeb.ChatLive do
use MyAppWeb, :live_view
def mount(%{"room_id" => room_id}, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(MyApp.PubSub, "room:" <> room_id)
end
{:ok, assign(socket, messages: [], room_id: room_id)}
end
def handle_event("send", %{"text" => text}, socket) do
msg = %{text: text, user: socket.assigns.current_user}
Phoenix.PubSub.broadcast(MyApp.PubSub, "room:" <> socket.assigns.room_id, {:msg, msg})
{:noreply, socket}
end
def handle_info({:msg, msg}, socket) do
{:noreply, update(socket, :messages, &(&1 ++ [msg]))}
end
end
LiveComponent
defmodule MyAppWeb.CardComponent do
use MyAppWeb, :live_component
def render(assigns) do
~H"""
<div id={"card-" <> @item.id}>
<h3><%= @item.title %></h3>
<button phx-click="like" phx-target={@myself}>Like (<%= @likes %>)</button>
</div>
"""
end
def handle_event("like", _params, socket) do
{:noreply, update(socket, :likes, &(&1 + 1))}
end
end
用于客户端互操作的 JS 钩子
// app.js
let Hooks = {}
Hooks.Chart = {
mounted() {
this.chart = new Chart(this.el, { type: 'line', data: JSON.parse(this.el.dataset.values) })
this.handleEvent("update_chart", ({values}) => {
this.chart.data.datasets[0].data = values
this.chart.update()
})
}
}
<canvas id="my-chart" phx-hook="Chart" data-values={Jason.encode!(@chart_data)}></canvas>
部署到 Fly.io
fly launch
fly deploy
fly scale count 2
LiveView 应用在 Fly.io 上通过 libcluster 集群分发 WebSocket 连接。