LiveView简介 #

一、LiveView概述 #

1.1 什么是LiveView #

LiveView是Phoenix的实时视图技术,允许在服务端渲染富交互应用,无需编写JavaScript。

1.2 LiveView特点 #

  • 服务端渲染
  • 实时更新
  • 无需JavaScript
  • WebSocket通信
  • 最小化DOM更新

1.3 LiveView架构 #

text
浏览器
    │
    ▼
WebSocket连接
    │
    ▼
LiveView进程
    │
    ├── 状态管理
    ├── 事件处理
    └── 模板渲染
    │
    ▼
DOM补丁更新

二、创建LiveView #

2.1 定义LiveView #

elixir
defmodule HelloWeb.CounterLive do
  use HelloWeb, :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="increment">+</button>
      <button phx-click="decrement">-</button>
    </div>
    """
  end

  def handle_event("increment", _params, socket) do
    {:noreply, update(socket, :count, &(&1 + 1))}
  end

  def handle_event("decrement", _params, socket) do
    {:noreply, update(socket, :count, &(&1 - 1))}
  end
end

2.2 路由配置 #

elixir
defmodule HelloWeb.Router do
  use Phoenix.Router

  live "/counter", CounterLive
  live "/users", UserLive.Index
  live "/users/:id", UserLive.Show
end

2.3 生成LiveView资源 #

bash
mix phx.gen.live Accounts User users name:string email:string

三、生命周期 #

3.1 mount/3 #

elixir
def mount(_params, _session, socket) do
  if connected?(socket) do
    Phoenix.PubSub.subscribe(Hello.PubSub, "updates")
  end

  {:ok, assign(socket, :users, Hello.Accounts.list_users())}
end

3.2 handle_params/3 #

elixir
def handle_params(params, _url, socket) do
  page = Map.get(params, "page", "1") |> String.to_integer()
  users = Hello.Accounts.list_users(page: page)

  {:noreply, assign(socket, :users, users)}
end

3.3 render/1 #

elixir
def render(assigns) do
  ~H"""
  <div>
    <ul>
      <%= for user <- @users do %>
        <li><%= user.name %> - <%= user.email %></li>
      <% end %>
    </ul>
  </div>
  """
end

3.4 terminate/2 #

elixir
def terminate(_reason, socket) do
  cleanup_resources(socket)
  :ok
end

四、状态管理 #

4.1 assign/3 #

elixir
def mount(_params, _session, socket) do
  socket =
    socket
    |> assign(:users, [])
    |> assign(:page, 1)
    |> assign(:total_pages, 0)

  {:ok, socket}
end

4.2 assign_new/3 #

elixir
def mount(_params, session, socket) do
  socket =
    socket
    |> assign_new(:current_user, fn ->
      get_user_from_session(session)
    end)

  {:ok, socket}
end

4.3 update/3 #

elixir
def handle_event("increment", _params, socket) do
  {:noreply, update(socket, :count, &(&1 + 1))}
end

def handle_event("add_user", %{"name" => name}, socket) do
  {:noreply, update(socket, :users, fn users -> [name | users] end)}
end

五、事件处理 #

5.1 点击事件 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end
elixir
def handle_event("delete", %{"id" => id}, socket) do
  {:ok, _} = Hello.Accounts.delete_user(id)
  {:noreply, assign(socket, :users, Hello.Accounts.list_users())}
end

5.2 表单事件 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end
elixir
def handle_event("validate", %{"user" => user_params}, socket) do
  changeset = Hello.Accounts.change_user(%User{}, user_params)
  {:noreply, assign(socket, :changeset, changeset)}
end

def handle_event("save", %{"user" => user_params}, socket) do
  case Hello.Accounts.create_user(user_params) do
    {:ok, user} ->
      {:noreply,
       socket
       |> put_flash(:info, "User created")
       |> push_navigate(to: ~p"/users/#{user.id}")}

    {:error, changeset} ->
      {:noreply, assign(socket, :changeset, changeset)}
  end
end

5.3 键盘事件 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end
elixir
def handle_event("search", %{"value" => query}, socket) do
  users = Hello.Accounts.search_users(query)
  {:noreply, assign(socket, :users, users)}
end

5.4 焦点事件 #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

六、导航 #

6.1 push_navigate #

elixir
def handle_event("redirect", _params, socket) do
  {:noreply, push_navigate(socket, to: ~p"/users")}
end

6.2 push_patch #

elixir
def handle_event("change_page", %{"page" => page}, socket) do
  {:noreply, push_patch(socket, to: ~p"/users?page=#{page}")}
end

6.3 redirect #

elixir
def handle_event("external_redirect", _params, socket) do
  {:noreply, redirect(socket, external: "https://example.com")}
end

七、Flash消息 #

7.1 设置Flash #

elixir
def handle_event("save", %{"user" => user_params}, socket) do
  case Hello.Accounts.create_user(user_params) do
    {:ok, user} ->
      {:noreply,
       socket
       |> put_flash(:info, "User created successfully")
       |> push_navigate(to: ~p"/users/#{user.id}")}

    {:error, changeset} ->
      {:noreply,
       socket
       |> put_flash(:error, "Failed to create user")
       |> assign(:changeset, changeset)}
  end
end

7.2 显示Flash #

heex
defmodule HelloWeb.Layouts do
  use HelloWeb, :html

  embed_templates "layouts/*"
end

八、定时器 #

8.1 定时更新 #

elixir
def mount(_params, _session, socket) do
  if connected?(socket) do
    :timer.send_interval(1000, :tick)
  end

  {:ok, assign(socket, :time, DateTime.utc_now())}
end

def handle_info(:tick, socket) do
  {:noreply, assign(socket, :time, DateTime.utc_now())}
end

8.2 倒计时 #

elixir
def mount(_params, _session, socket) do
  if connected?(socket) do
    Process.send_after(self(), :countdown, 1000)
  end

  {:ok, assign(socket, :seconds, 60)}
end

def handle_info(:countdown, socket) do
  if socket.assigns.seconds > 0 do
    Process.send_after(self(), :countdown, 1000)
    {:noreply, update(socket, :seconds, &(&1 - 1))}
  else
    {:noreply, socket}
  end
end

九、PubSub集成 #

9.1 订阅消息 #

elixir
def mount(_params, _session, socket) do
  if connected?(socket) do
    Phoenix.PubSub.subscribe(Hello.PubSub, "users")
  end

  {:ok, assign(socket, :users, Hello.Accounts.list_users())}
end

def handle_info({:user_created, user}, socket) do
  {:noreply, update(socket, :users, fn users -> [user | users] end)}
end

def handle_info({:user_deleted, user_id}, socket) do
  {:noreply, update(socket, :users, fn users ->
    Enum.reject(users, fn u -> u.id == user_id end)
  end)}
end

9.2 广播消息 #

elixir
def handle_event("create_user", %{"user" => user_params}, socket) do
  case Hello.Accounts.create_user(user_params) do
    {:ok, user} ->
      Phoenix.PubSub.broadcast(Hello.PubSub, "users", {:user_created, user})
      {:noreply, push_navigate(socket, to: ~p"/users/#{user.id}")}

    {:error, changeset} ->
      {:noreply, assign(socket, :changeset, changeset)}
  end
end

十、总结 #

10.1 核心概念 #

概念 说明
mount 初始化状态
render 渲染模板
handle_event 处理事件
handle_info 处理消息
assign 状态管理

10.2 常用绑定 #

绑定 说明
phx-click 点击事件
phx-change 表单变化
phx-submit 表单提交
phx-key 键盘事件
phx-hook JavaScript钩子

10.3 下一步 #

现在你已经了解了LiveView基础,接下来让我们学习 LiveView组件,深入了解组件化开发!

最后更新:2026-03-28