正在加载,请稍候…

Elixir Phoenix LiveView:无需JavaScript构建实时应用

使用Phoenix LiveView构建实时Web应用——状态化服务端组件、PubSub、LiveComponent、JS钩子,以及部署到Fly.io。

Elixir Phoenix LiveView:无需JavaScript构建实时应用

Phoenix LiveView 理念

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

Elixir Phoenix LiveView:无需JavaScript构建实时应用 插图

基础计数器 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

Elixir Phoenix LiveView:无需JavaScript构建实时应用 插图

使用 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

Elixir Phoenix LiveView:无需JavaScript构建实时应用 插图

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 连接。