查看源代码 欢迎

欢迎来到 Phoenix LiveView 文档。Phoenix LiveView 能够使用服务器渲染的 HTML 创建丰富的实时用户体验。有关 LiveView 及其优势的概述,请参见 我们的 README

什么是 LiveView?

LiveView 是接收事件、更新其状态并以差异形式将更新渲染到页面的进程。

LiveView 编程模型是声明式的:它不是说“事件 X 发生后,在页面上更改 Y”,LiveView 中的事件是可能导致状态更改的常规消息。状态发生变化后,LiveView 将重新渲染其 HTML 模板的相关部分并将其推送到浏览器,浏览器将以最有效的方式更新页面。

LiveView 状态不过是一些函数式且不可变的 Elixir 数据结构。事件要么是内部应用程序消息(通常由 Phoenix.PubSub 发出),要么是客户端/浏览器发送的。

每个 LiveView 首先作为常规 HTTP 请求的一部分以静态方式渲染,这可以为“首次有意义的绘制”提供更快的响应时间,此外还可以帮助搜索和索引引擎。然后在客户端和服务器之间建立一个持久连接。这使 LiveView 应用程序能够更快地响应用户事件,因为与无状态请求相比,需要完成的工作更少,需要发送的数据更少,无状态请求必须在每次请求时进行身份验证、解码、加载和编码数据。

示例

LiveView 默认情况下包含在 Phoenix 应用程序中。因此,要使用 LiveView,您必须已经安装 Phoenix 并创建了您的第一个应用程序。如果您尚未完成此操作,请查看 Phoenix 的安装指南 以开始使用。

LiveView 的行为由一个模块来概述,该模块实现一系列函数作为回调。让我们看一个示例。将下面的文件写入 lib/my_app_web/live/thermostat_live.ex

defmodule MyAppWeb.ThermostatLive do
  # In Phoenix v1.6+ apps, the line is typically: use MyAppWeb, :live_view
  use Phoenix.LiveView

  def render(assigns) do
    ~H"""
    Current temperature: <%= @temperature %>°F
    <button phx-click="inc_temperature">+</button>
    """
  end

  def mount(_params, _session, socket) do
    temperature = 70 # Let's assume a fixed temperature for now
    {:ok, assign(socket, :temperature, temperature)}
  end

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

上面的模块定义了三个函数(它们是 LiveView 要求的回调)。第一个是 render/1,它接收 socket assigns,负责返回要在页面上渲染的内容。我们使用 ~H 符号来定义 HEEx 模板,它代表 HTML+EEx。它们是 Elixir 内置 EEx 模板的扩展,支持 HTML 验证、基于语法的组件、智能更改跟踪等。您可以在 Phoenix.Component.sigil_H/2 中了解有关模板语法的更多信息(请注意,当您使用 Phoenix.LiveView 时,会自动导入 Phoenix.Component)。

渲染时使用的数据来自 mount 回调。当 LiveView 启动时,将调用 mount 回调。在其中,您可以访问请求参数、读取存储在会话中的信息(通常是识别当前用户的身份的信息)以及一个 socket。socket 是我们保存所有状态的地方,包括分配。 mount 继续将默认温度分配给 socket。由于 Elixir 数据结构是不可变的,因此 LiveView API 通常会接收 socket 并返回一个更新后的 socket。然后我们返回 {:ok, socket} 来表示我们能够成功安装 LiveView。在 mount 之后,LiveView 将使用来自 assigns 的值渲染页面并将其发送到客户端。

如果您查看渲染的 HTML,您会注意到有一个带有 phx-click 属性的按钮。单击该按钮时,一个 "inc_temperature" 事件将发送到服务器,该事件将匹配并由 handle_event 回调处理。此回调更新 socket 并返回带有更新后的 socket 的 {:noreply, socket}。LiveView 中(以及 Elixir 中的一般情况)的 handle_* 回调是根据某些操作来调用的,在本例中,用户单击了一个按钮。{:noreply, socket} 返回表示没有其他回复发送到浏览器,只是渲染了页面的新版本。LiveView 然后计算差异并将其发送到客户端。

现在我们已准备好渲染 LiveView。您可以直接从路由器提供 LiveView

defmodule MyAppWeb.Router do
  use Phoenix.Router
  import Phoenix.LiveView.Router

  scope "/", MyAppWeb do
    live "/thermostat", ThermostatLive
  end
end

渲染 LiveView 后,将发送常规 HTML 响应。在您的 app.js 文件中,您应该找到以下内容

import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
liveSocket.connect()

现在 JavaScript 客户端将通过 WebSocket 连接,并且 mount/3 将在生成的 LiveView 进程中调用。

参数和会话

mount 回调接收三个参数:请求参数、会话和 socket。

参数可用于从 URL 读取信息。例如,假设您在某个地方定义了一个 Thermostat 模块,它可以根据房屋名称读取此信息,您可以这样写

def mount(%{"house" => house}, _session, socket) do
  temperature = Thermostat.get_house_reading(house)
  {:ok, assign(socket, :temperature, temperature)}
end

然后在您的路由器中

live "/thermostat/:house", ThermostatLive

会话从签名(或加密)的 cookie 中检索信息。这是您可以存储身份验证信息的地方,例如 current_user_id

def mount(_params, %{"current_user_id" => user_id}, socket) do
  temperature = Thermostat.get_user_reading(user_id)
  {:ok, assign(socket, :temperature, temperature)}
end

Phoenix 带有内置的身份验证生成器。参见 mix phx.gen.auth

大多数情况下,在实践中,您将同时使用两者

def mount(%{"house" => house}, %{"current_user_id" => user_id}, socket) do
  temperature = Thermostat.get_house_reading(user_id, house)
  {:ok, assign(socket, :temperature, temperature)}
end

换句话说,只要用户有权访问,您就需要读取有关特定房屋的信息。

绑定

Phoenix 支持 DOM 元素绑定以进行客户端-服务器交互。例如,要对按钮上的点击做出反应,您将渲染该元素

<button phx-click="inc_temperature">+</button>

然后在服务器上,所有 LiveView 绑定都由 handle_event/3 回调处理,例如

def handle_event("inc_temperature", _value, socket) do
  {:noreply, update(socket, :temperature, &(&1 + 1))}
end

要更新 UI 状态(例如,打开和关闭下拉菜单、切换选项卡等),LiveView 还支持 JS 命令(Phoenix.LiveView.JS),这些命令直接在客户端执行,而无需到达服务器。要了解有关此的更多信息,请参见 我们的绑定页面,了解所有 LiveView 绑定的完整列表,以及我们的 JavaScript 互操作性指南

LiveView 内置支持表单,包括上传和关联管理。参见 Phoenix.Component.form/1 作为起点,以及 Phoenix.Component.inputs_for/1 了解如何处理关联。 上传表单绑定 指南提供了有关高级功能的更多信息。

LiveView 提供功能,允许使用 浏览器的 pushState API 进行页面导航。使用实时导航,页面将更新,而无需完全刷新页面。

您可以修补当前 LiveView,更新其 URL,或导航到新的 LiveView。您可以在 实时导航 指南中了解有关它们的更多信息。

生成器

Phoenix v1.6 及更高版本包含用于 LiveView 的代码生成器。如果您想查看如何构建应用程序的示例,从数据库一直到 LiveView,请运行以下命令

mix phx.gen.live Blog Post posts title:string body:text

有关更多信息,请运行 mix help phx.gen.live

对于身份验证,内置了 LiveView 支持,请运行 mix phx.gen.auth Account User users

将状态、标记和事件在 LiveView 中进行分隔

LiveView 支持两种扩展机制:函数组件(由 HEEx 模板提供)和有状态组件。

函数组件是接收 assigns 映射的任何函数,类似于我们 LiveView 中的 render(assigns),并返回一个 ~H 模板。例如

def weather_greeting(assigns) do
  ~H"""
  <div title="My div" class={@class}>
    <p>Hello <%= @name %></p>
    <MyApp.Weather.city name="Kraków"/>
  </div>
  """
end

您可以在 Phoenix.Component 模块中了解有关函数组件的更多信息。归根结底,它们是重用 LiveView 中标记的实用机制。

但是,有时您需要分隔或重用的内容不仅是标记。也许您想将 LiveView 中的一部分状态或部分事件移到另一个模块中。对于这些情况,LiveView 提供了 Phoenix.LiveComponent,这些组件使用 live_component/1 渲染

<.live_component module={UserComponent} id={user.id} user={user} />

组件有自己的 mount/3handle_event/3 回调,以及自己的具有更改跟踪支持的状态。组件也很轻量级,因为它们在与父 LiveView 相同的进程中“运行”。但是,这意味着组件中的错误会导致整个视图无法渲染。参见 Phoenix.LiveComponent,了解有关组件的完整说明。

最后,如果您希望在 LiveView 的各个部分之间完全隔离,您始终可以通过调用 live_render/3 在另一个 LiveView 中渲染 LiveView。此子 LiveView 在与父级不同的进程中运行,并具有自己的回调。如果子 LiveView 崩溃,它不会影响父级。如果父级崩溃,则所有子级都会被终止。

渲染子 LiveView 时,:id 选项是必需的,以唯一标识子级。子 LiveView 只会渲染和安装一次,前提是其 ID 保持不变。要强制子级使用新的会话数据重新安装,必须提供新的 ID。

鉴于 LiveView 在自己的进程中运行,它是创建完全隔离的 UI 元素的绝佳工具,但如果您只想分隔标记或事件(或两者),它是一个略微昂贵的抽象。

总结一下

指南

此文档分为两类。我们有所有 LiveView 模块的 API 参考,您可以在其中详细了解 Phoenix.ComponentPhoenix.LiveView 等。

LiveView 还提供了许多指南来帮助您完成旅程,这些指南分为服务器端和客户端

服务器端

这些指南侧重于服务器端功能

客户端

这些指南侧重于 LiveView 绑定和客户端集成