全书目录

第三十八篇:Phoenix 完全指南 —— 实时频道城邦宇宙

第九章:用一个实时调度台,真正看懂 LiveView

2 分钟 498 字 第 728 / 962 个阅读单元

我们做一个“城邦调度台”。

elixir
defmodule CityWeb.DispatchLive.Index do
  use CityWeb, :live_view

  alias City.Dispatch

  @impl true
  def mount(_params, _session, socket) do
    scope = socket.assigns.current_scope
    topic = "org:#{scope.user.organization_id}:dispatches"

    if connected?(socket) do
      Phoenix.PubSub.subscribe(City.PubSub, topic)
    end

    {:ok,
     socket
     |> assign(:form, to_form(%{"title" => ""}, as: :dispatch))
     |> stream(:dispatches, Dispatch.list_dispatches(scope))}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <Layouts.app flash={@flash} current_scope={@current_scope}>
      <section>
        <h1>实时调度台</h1>

        <.form for={@form} phx-submit="save">
          <.input field={@form[:title]} label="新事件标题" />
          <.button>登记事件</.button>
        </.form>

        <ul id="dispatches" phx-update="stream">
          <li :for={{dom_id, dispatch} <- @streams.dispatches} id={dom_id}>
            <strong>{dispatch.title}</strong>
            <span>{dispatch.status}</span>
          </li>
        </ul>
      </section>
    </Layouts.app>
    """
  end

  @impl true
  def handle_event("save", %{"dispatch" => params}, socket) do
    case Dispatch.create_dispatch(socket.assigns.current_scope, params) do
      {:ok, _dispatch} ->
        {:noreply, assign(socket, :form, to_form(%{"title" => ""}, as: :dispatch))}

      {:error, changeset} ->
        {:noreply, assign(socket, :form, to_form(changeset, as: :dispatch))}
    end
  end

  @impl true
  def handle_info({:dispatch_created, dispatch}, socket) do
    {:noreply, stream_insert(socket, :dispatches, dispatch, at: 0)}
  end
end

这段代码应该怎么读?

#1. mount/3 是中控室开机
  • 初始化表单
  • 拉首批数据
  • 在连接建立后订阅广播主题

为什么 connected?(socket) 很重要?

因为 LiveView 首次是“断开态静态渲染”,连接建立后才进入“持续值守态”。
很多需要订阅、定时器、外部监听的事,都应该在连上之后再做。

#2. handle_event/3 是市民按下按钮

按钮上的 phx-submit="save" 会把事件发到服务器。
LiveView 不要求你在浏览器里维护一套复杂状态机;它让服务器端进程直接接这件事。

#3. handle_info/2 是城内广播到达

当 Context 通过 PubSub 广播 {:dispatch_created, dispatch} 时,这个 LiveView 收到消息,更新自己的流。

#4. stream/3stream_insert/4 是大名单优化

它的意义不是“另一个 list assign”,而是:

让大集合主要留在客户端 DOM 里,服务器不必永远抱着整份列表。

这对实时列表、时间线、通知流、消息流非常重要。