查看源代码 Phoenix.LiveView 行为 (Phoenix LiveView v0.20.17)

LiveView 是一个进程,它接收事件,更新其状态,并将页面更新以 diff 的形式渲染到页面。

要开始使用,请查看 欢迎指南。该模块提供了有关使用 LiveView 的高级文档和功能。

生命周期

LiveView 从一个普通的 HTTP 请求和 HTML 响应开始,然后在客户端连接时升级为一个有状态的视图,即使禁用 JavaScript 也能保证一个正常的 HTML 页面。任何时候,当一个有状态的视图发生更改或更新其 socket 赋值时,它都会自动重新渲染,并将更新推送到客户端。

Socket 赋值是保存在服务器端 Phoenix.LiveView.Socket 中的有状态值。这不同于常见的无状态 HTTP 模式,即以令牌或 cookie 的形式将连接状态发送到客户端,并在服务器端重建状态以服务每个请求。

您通常从路由器开始渲染 LiveView。当 LiveView 第一次渲染时,mount/3 回调会用当前参数、当前会话和 LiveView socket 调用。就像在普通请求中一样,params 包含用户可以修改的公共数据。 session 始终包含应用程序本身设置的私有数据。 mount/3 回调连接必要的 socket 赋值以渲染视图。装载完成后,handle_params/3 会被调用,以便处理 uri 和查询参数。最后,render/1 会被调用,并将 HTML 作为普通的 HTML 响应发送到客户端。

渲染静态页面后,LiveView 从客户端连接到服务器,在服务器上生成有状态的视图,以将渲染的更新推送到浏览器,并通过 phx- 绑定接收客户端事件。就像第一次渲染一样,mount/3 会用参数、会话和 socket 状态调用。但是,在连接的客户端情况下,一个 LiveView 进程会在服务器上生成,运行 handle_params/3,然后将 render/1 的结果推送到客户端,并在连接期间继续运行。如果在有状态生命周期的任何时候遇到崩溃,或者客户端连接断开,客户端会优雅地重新连接到服务器,再次调用 mount/3handle_params/3

LiveView 还允许使用 attach_hook/4 将钩子附加到特定的生命周期阶段。

模板并置

在 LiveView 中,有两种渲染内容的可能方法。第一种是通过显式定义一个渲染函数,它接收 assigns 并返回一个使用 ~H 标记 定义的 HEEx 模板。

defmodule MyAppWeb.DemoLive do
  use Phoenix.LiveView

  def render(assigns) do
    ~H"""
    Hello world!
    """
  end
end

对于较大的模板,您可以将它们放在与 LiveView 相同目录和相同名称的文件中。例如,如果上面的文件放在 lib/my_app_web/live/demo_live.ex 中,您还可以完全删除 render/1 函数,并将模板代码放在 lib/my_app_web/live/demo_live.html.heex 中。

异步操作

在 LiveView 和 LiveComponent 中,执行异步工作很常见。它允许用户快速获得一个可用的 UI,而系统在后台获取一些数据或与外部服务通信,而不会阻塞渲染或事件处理。对于异步工作,您通常还需要处理异步操作的不同状态,例如加载、错误和成功的结果。您还需要捕获任何错误或退出,并将其转换为 UI 中有意义的更新,而不是让用户体验崩溃。

异步赋值

assign_async/3 函数接收 socket、将被异步赋值的键或键列表以及一个函数。这个函数将被 assign_async 包裹在一个 task 中,使您能够轻松地返回结果。这个函数必须返回一个 {:ok, assigns}{:error, reason} 元组,其中 assigns 是传递给 assign_async 的键的映射。如果该函数返回其他任何内容,则会引发错误。

该任务只有在 socket 连接时才会开始。

例如,假设我们要异步从数据库中获取用户的组织,以及他们的个人资料和排名

def mount(%{"slug" => slug}, _, socket) do
  {:ok,
   socket
   |> assign(:foo, "bar")
   |> assign_async(:org, fn -> {:ok, %{org: fetch_org!(slug)}} end)
   |> assign_async([:profile, :rank], fn -> {:ok, %{profile: ..., rank: ...}} end)}
end

警告

在使用异步操作时,重要的是不要将 socket 传递到函数中,因为它会将整个 socket 结构体复制到 Task 进程,这可能会非常昂贵。

代替

assign_async(:org, fn -> {:ok, %{org: fetch_org(socket.assigns.slug)}} end)

我们应该这样做

slug = socket.assigns.slug
assign_async(:org, fn -> {:ok, %{org: fetch_org(slug)}} end)

查看:https://hexdocs.erlang.ac.cn/elixir/process-anti-patterns.html#sending-unnecessary-data

异步操作的状态存储在 socket 赋值中的 Phoenix.LiveView.AsyncResult 中。它包含加载和失败状态,以及结果。例如,如果我们要在 UI 中显示 :org 的加载状态,我们的模板可以有条件地渲染这些状态

<div :if={@org.loading}>Loading organization...</div>
<div :if={org = @org.ok? && @org.result}><%= org.name %> loaded!</div>

Phoenix.Component.async_result/1 函数组件也可以用于使用槽位声明性地渲染不同的状态

<.async_result :let={org} assign={@org}>
  <:loading>Loading organization...</:loading>
  <:failed :let={_failure}>there was an error loading the organization</:failed>
  <%= org.name %>
</.async_result>

任意异步操作

有时您需要更低级别的异步操作控制,同时仍然接收进程隔离和错误处理。为此,您可以使用 start_async/3Phoenix.LiveView.AsyncResult 模块直接使用

def mount(%{"id" => id}, _, socket) do
  {:ok,
   socket
   |> assign(:org, AsyncResult.loading())
   |> start_async(:my_task, fn -> fetch_org!(id) end)}
end

def handle_async(:my_task, {:ok, fetched_org}, socket) do
  %{org: org} = socket.assigns
  {:noreply, assign(socket, :org, AsyncResult.ok(org, fetched_org))}
end

def handle_async(:my_task, {:exit, reason}, socket) do
  %{org: org} = socket.assigns
  {:noreply, assign(socket, :org, AsyncResult.failed(org, {:exit, reason}))}
end

start_async/3 用于异步获取组织。当任务完成或退出时,handle_async/3 回调会被调用,结果被包裹在 {:ok, result}{:exit, reason} 中。 AsyncResult 模块提供用于更新异步操作状态的函数,但是如果您想自己处理状态,也可以直接将任何值分配给 socket。

端点配置

LiveView 接受您在端点中的 :live_view 键下进行的以下配置

  • :signing_salt (必需) - 用于对发送到客户端的数据进行签名的盐

  • :hibernate_after (可选) - LiveView 在压缩自身内存和状态之前允许的空闲时间(以毫秒为单位)。默认为 15000ms (15 秒)

总结

回调

start_async/3 操作的结果可用时调用。

调用以处理来自其他 Elixir 进程的调用。

调用以处理来自其他 Elixir 进程的投递。

调用以处理客户端发送的事件。

调用以处理来自其他 Elixir 进程的消息。

在装载后以及每次出现实时补丁事件时调用。

LiveView 的入口点。

渲染模板。

当 LiveView 终止时调用。

函数

定义 LiveView 的元数据。

在当前模块中使用 LiveView 以将其标记为 LiveView。

允许提供的名称的上传。

通过 name 为生命周期 stage 将给定的 fun 附加到 socket 中。

取消给定条目的上传。

清除闪存。

从闪存中清除一个键。

如果 socket 已连接,则返回 true。

使用单个上传的条目。

从生命周期 stage 中分离具有给定 name 的钩子。

撤销先前从 allow_upload/3 允许的上传。

访问 socket 中给定的连接信息键。

访问客户端发送的连接参数,以便在连接装载时使用。

声明一个模块回调,在 LiveView 装载时调用。

将事件推送到客户端。

注释 socket 以导航到另一个 LiveView。

注释 socket 以在当前 LiveView 中进行导航。

将闪存消息添加到 socket 以便显示。

在 socket 中放置一个新的私有键和值。

注释 socket 以重定向到目标路径。

配置用于渲染 LiveView/LiveComponent 的函数。

send_update/3 类似,但更新将根据给定的 time_in_milliseconds 延迟。

将您的函数包装在一个异步任务中,并调用一个回调 name 来处理结果。

如果 socket 已连接且跟踪的静态资产已更改,则返回 true。

将一个新流分配给 socket,或将条目插入到现有流中。返回一个更新后的 socket

从流中删除一个项目。

根据计算后的 DOM ID 从流中删除一个项目。

在流中插入一个新项目或更新一个现有项目。

返回套接字的传输 PID。

返回上传已完成和正在进行的条目。

类型

@type unsigned_params() :: map()

回调

链接到此回调

handle_async(name, async_fun_result, socket)

查看源代码 (可选)
@callback handle_async(
  name :: term(),
  async_fun_result :: {:ok, term()} | {:exit, term()},
  socket :: Phoenix.LiveView.Socket.t()
) :: {:noreply, Phoenix.LiveView.Socket.t()}

start_async/3 操作的结果可用时调用。

要深入了解使用此回调,请参阅"任意异步操作"部分。

链接到此回调

handle_call(msg, {}, socket)

查看源代码 (可选)
@callback handle_call(
  msg :: term(),
  {pid(), reference()},
  socket :: Phoenix.LiveView.Socket.t()
) ::
  {:noreply, Phoenix.LiveView.Socket.t()}
  | {:reply, term(), Phoenix.LiveView.Socket.t()}

调用以处理来自其他 Elixir 进程的调用。

有关更多信息,请参见GenServer.call/3GenServer.handle_call/3

链接到此回调

handle_cast(msg, socket)

查看源代码 (可选)
@callback handle_cast(msg :: term(), socket :: Phoenix.LiveView.Socket.t()) ::
  {:noreply, Phoenix.LiveView.Socket.t()}

调用以处理来自其他 Elixir 进程的投递。

有关更多信息,请参见GenServer.cast/2GenServer.handle_cast/2。它必须始终返回{:noreply, socket},其中:noreply表示没有向发送消息的过程发送其他信息。

链接到此回调

handle_event(event, unsigned_params, socket)

查看源代码 (可选)
@callback handle_event(
  event :: binary(),
  unsigned_params(),
  socket :: Phoenix.LiveView.Socket.t()
) ::
  {:noreply, Phoenix.LiveView.Socket.t()}
  | {:reply, map(), Phoenix.LiveView.Socket.t()}

调用以处理客户端发送的事件。

它接收event名称、事件有效负载(作为映射)和套接字。

它必须返回{:noreply, socket},其中:noreply表示没有向客户端发送其他信息,或者{:reply, map(), socket},其中给定的map()被编码并作为回复发送给客户端。

链接到此回调

handle_info(msg, socket)

查看源代码 (可选)
@callback handle_info(msg :: term(), socket :: Phoenix.LiveView.Socket.t()) ::
  {:noreply, Phoenix.LiveView.Socket.t()}

调用以处理来自其他 Elixir 进程的消息。

有关更多信息,请参见Kernel.send/2GenServer.handle_info/2。它必须始终返回{:noreply, socket},其中:noreply表示没有向发送消息的过程发送其他信息。

链接到此回调

handle_params(unsigned_params, uri, socket)

查看源代码 (可选)
@callback handle_params(
  unsigned_params(),
  uri :: String.t(),
  socket :: Phoenix.LiveView.Socket.t()
) ::
  {:noreply, Phoenix.LiveView.Socket.t()}

在装载后以及每次出现实时补丁事件时调用。

它接收当前的params(包括来自路由器的参数)、来自客户端的当前uri以及socket。它在安装后或每当由push_patch/2<.link patch={...}>引起的实时导航事件发生时被调用。

它必须始终返回{:noreply, socket},其中:noreply表示没有向客户端发送其他信息。

链接到此回调

mount(params, session, socket)

查看源代码 (可选)
@callback mount(
  params :: unsigned_params() | :not_mounted_at_router,
  session :: map(),
  socket :: Phoenix.LiveView.Socket.t()
) ::
  {:ok, Phoenix.LiveView.Socket.t()}
  | {:ok, Phoenix.LiveView.Socket.t(), keyword()}

LiveView 的入口点。

对于模板根目录中的每个 LiveView,mount/3 被调用两次:一次用于执行初始页面加载,另一次用于建立实时套接字。

它期望三个参数

  • params - 一个包含字符串键的映射,这些键包含用户可以设置的公共信息。该映射包含查询参数以及任何路由器路径参数。如果 LiveView 未在路由器上安装,则此参数是原子:not_mounted_at_router
  • session - 连接会话
  • socket - LiveView 套接字

它必须返回{:ok, socket}{:ok, socket, options},其中options是以下之一

  • :temporary_assigns - 一系列关键字,这些关键字是在每次渲染后必须重置为其值的临时分配。请注意,一旦值被重置,它将不会再次重新渲染,直到显式分配它

  • :layout - LiveView 要使用的可选布局。设置此选项将覆盖之前通过Phoenix.LiveView.Router.live_session/2或在use Phoenix.LiveView上设置的任何布局

链接到此回调

render(assigns)

查看源代码 (可选)
@callback render(assigns :: Phoenix.LiveView.Socket.assigns()) ::
  Phoenix.LiveView.Rendered.t()

渲染模板。

每当 LiveView 检测到必须渲染并发送到客户端的新内容时,就会调用此回调。

如果您定义了此函数,它必须返回通过Phoenix.Component.sigil_H/2定义的模板。

如果您未定义此函数,LiveView 将尝试在与您的 LiveView 相同的目录中渲染模板。例如,如果您在lib/my_app/live_views/my_custom_view.ex中有一个名为MyApp.MyCustomView的 LiveView,Phoenix 将在lib/my_app/live_views/my_custom_view.html.heex中查找模板。

链接到此回调

terminate(reason, socket)

查看源代码 (可选)
@callback terminate(reason, socket :: Phoenix.LiveView.Socket.t()) :: term()
when reason: :normal | :shutdown | {:shutdown, :left | :closed | term()}

当 LiveView 终止时调用。

在发生错误的情况下,只有当 LiveView 正在捕获退出时才会调用此回调。有关更多信息,请参见GenServer.terminate/2

函数

定义 LiveView 的元数据。

这必须从__live__回调中返回。

它接受

  • :container - 用于 LiveView 容器的 HTML 标签和 DOM 属性的可选元组。例如:{:li, style: "color: blue;"}

  • :layout - 配置 LiveView 将在其中渲染的布局。此布局可以在mount/3上或通过Phoenix.LiveView.Router.live_session/2中的:layout选项覆盖

  • :log - 为 LiveView 配置日志级别,可以是false或日志级别

  • :on_mount - 一系列元组,其中包含要作为on_mount钩子调用的模块名称和参数

在当前模块中使用 LiveView 以将其标记为 LiveView。

use Phoenix.LiveView,
  container: {:tr, class: "colorized"},
  layout: {MyAppWeb.Layouts, :app},
  log: :info

选项

  • :container - 用于 LiveView 容器的 HTML 标签和 DOM 属性的可选元组。例如:{:li, style: "color: blue;"}。有关更多信息和示例,请参见Phoenix.Component.live_render/3

  • :global_prefixes - 用于组件的全局前缀。有关更多信息,请参见Phoenix.Component中的“全局属性”。

  • :layout - 配置 LiveView 将在其中渲染的布局。此布局可以在mount/3上或通过Phoenix.LiveView.Router.live_session/2中的:layout选项覆盖

  • :log - 为 LiveView 配置日志级别,可以是false或日志级别

链接到此函数

allow_upload(socket, name, options)

查看源代码

允许提供的名称的上传。

选项

  • :accept - 必需。唯一文件扩展名(例如“.jpeg”)或 MIME 类型(例如“image/jpeg”或“image/*”)的列表。您也可以传递原子:any而不是列表,以支持允许任何类型的文件。例如,[".jpeg"]:any等。

  • :max_entries - 每个文件输入允许选择的最大文件数。默认为 1。

  • :max_file_size - 允许上传的最大文件大小(以字节为单位)。默认为 8MB。例如,12_000_000

  • :chunk_size - 上传时发送的块大小(以字节为单位)。默认为64_000

  • :chunk_timeout - 当未收到新块时,在关闭上传通道之前等待的时间(以毫秒为单位)。默认为10_000

  • :external - 用于为外部客户端上传器生成元数据的 2 元函数。此函数必须返回{:ok, meta, socket}{:error, meta, socket},其中 meta 是一个映射。有关示例用法,请参见“上传”部分。

  • :progress - 用于接收进度事件的可选 3 元函数。

  • :auto_upload - 指示客户端在选择文件时自动上传文件,而不是等待表单提交。默认为false

  • :writer - 实现Phoenix.LiveView.UploadWriter行为的模块,用于写入上传的块。默认为写入临时文件以供使用。有关自定义用法,请参见Phoenix.LiveView.UploadWriter 文档。

当先前允许的相同名称的上传仍在活动状态时,会引发异常。

示例

allow_upload(socket, :avatar, accept: ~w(.jpg .jpeg), max_entries: 2)
allow_upload(socket, :avatar, accept: :any)

为了在上传文件时自动使用文件,您可以将auto_upload: true与自定义进度函数配对,以在完成条目时使用它们。例如

allow_upload(socket, :avatar, accept: :any, progress: &handle_progress/3, auto_upload: true)

defp handle_progress(:avatar, entry, socket) do
  if entry.done? do
    uploaded_file =
      consume_uploaded_entry(socket, entry, fn %{} = meta ->
        {:ok, ...}
      end)

    {:noreply, put_flash(socket, :info, "file #{uploaded_file.name} uploaded")}
  else
    {:noreply, socket}
  end
end
链接到此宏

assign_async(socket, key_or_keys, func, opts \\ [])

查看源代码 (宏)

异步分配键。

将您的函数包装在一个与调用者链接的任务中,错误会被包装。传递给assign_async/3的每个键都将被分配给一个%AsyncResult{}结构,该结构包含操作的状态以及函数完成时的结果。

该任务只有在 socket 连接时才会开始。

选项

  • :supervisor - 允许您指定一个Task.Supervisor 来监督任务。
  • :reset - 在异步操作为 true 时删除先前的结果。可能的值是truefalse或要重置的键列表。默认为false

示例

def mount(%{"slug" => slug}, _, socket) do
  {:ok,
   socket
   |> assign(:foo, "bar")
   |> assign_async(:org, fn -> {:ok, %{org: fetch_org!(slug)}} end)
   |> assign_async([:profile, :rank], fn -> {:ok, %{profile: ..., rank: ...}} end)}
end

查看模块文档以了解更多信息。

assign_async/3send_update/3

由于 assign_async/3 内部的代码在单独的进程中运行,因此 send_update(Component, data)assign_async/3 内无法使用,因为 send_update/2 假设它在 LiveView 进程内部运行。解决方法是显式地将更新发送到 LiveView。

parent = self()
assign_async(socket, :org, fn ->
  # ...
  send_update(parent, Component, data)
end)
链接到此函数

attach_hook(socket, name, stage, fun)

查看源代码

通过 name 为生命周期 stage 将给定的 fun 附加到 socket 中。

注意:此函数用于服务器端生命周期回调。对于客户端挂钩,请参阅 JS 交互指南

挂钩提供了一种机制,可以利用 LiveView 生命周期中的关键阶段来绑定/更新分配、在必要时拦截事件、补丁和常规消息,以及注入通用功能。在以下生命周期阶段中的任何一个上使用 attach_hook/1:handle_params:handle_event:handle_info:handle_async:after_render。要将挂钩附加到 :mount 阶段,请使用 on_mount/1

注意:目前 LiveComponents 中仅支持 :after_render:handle_event 挂钩。

返回值

生命周期挂钩在 LiveView 上调用特定生命周期回调之前立即发生。除 :after_render 外,挂钩可以返回 {:halt, socket} 来停止还原,否则它必须返回 {:cont, socket},以便操作可以继续,直到对当前阶段调用所有挂钩。

对于 :after_render 挂钩,必须返回 socket 本身。对套接字分配的任何更新 *不会* 触发对客户端的新渲染或差异计算。

停止生命周期

请注意,从挂钩中停止 *将停止整个生命周期阶段*。这意味着当挂钩返回 {:halt, socket} 时,LiveView 回调 *不会* 被调用。这有一些影响。

对插件作者的影响

在定义与特定回调匹配的插件时,*必须* 定义一个通配符子句,因为即使对于可能不感兴趣的事件,您的挂钩也会被调用。

对最终用户的影响

允许挂钩停止调用回调,这意味着您可以附加挂钩以在分离之前拦截特定事件,同时允许其他事件正常继续。

回复事件

附加到 :handle_event 阶段的挂钩可以通过返回 {:halt, reply, socket} 来回复客户端事件。这对于 JavaScript 交互性 尤其有用,因为客户端挂钩可以推送事件并接收回复。

示例

附加和分离挂钩

def mount(_params, _session, socket) do
  socket =
    attach_hook(socket, :my_hook, :handle_event, fn
      "very-special-event", _params, socket ->
        # Handle the very special event and then detach the hook
        {:halt, detach_hook(socket, :my_hook, :handle_event)}

      _event, _params, socket ->
        {:cont, socket}
    end)

  {:ok, socket}
end

回复客户端事件

# JavaScript:
# /**
#  * @type {Object.<string, import("phoenix_live_view").ViewHook>}
#  */
# let Hooks = {}
# Hooks.ClientHook = {
#   mounted() {
#     this.pushEvent("ClientHook:mounted", {hello: "world"}, (reply) => {
#       console.log("received reply:", reply)
#     })
#   }
# }
# let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, ...})

def render(assigns) do
  ~H"""
  <div id="my-client-hook" phx-hook="ClientHook"></div>
  """
end

def mount(_params, _session, socket) do
  socket =
    attach_hook(socket, :reply_on_client_hook_mounted, :handle_event, fn
      "ClientHook:mounted", params, socket ->
        {:halt, params, socket}

      _, _, socket ->
        {:cont, socket}
    end)

  {:ok, socket}
end
链接到此函数

cancel_async(socket, async_or_keys, reason \\ {:shutdown, :cancel})

查看源代码

如果存在,则取消异步操作。

接受使用 assign_async/3%AsyncResult{} 或者传递给 start_async/3 的键。

底层进程将使用提供的理由被终止,或者如果没有传递理由,则使用 {:shutdown, :cancel}。对于 assign_async/3 操作,:failed 字段将设置为 {:exit, reason}。对于 start_async/3handle_async/3 回调将接收 {:exit, reason} 作为结果。

返回 %Phoenix.LiveView.Socket{}

示例

cancel_async(socket, :preview)
cancel_async(socket, :preview, :my_reason)
cancel_async(socket, socket.assigns.preview)
链接到此函数

cancel_upload(socket, name, entry_ref)

查看源代码

取消给定条目的上传。

示例

<%= for entry <- @uploads.avatar.entries do %>
  ...
  <button phx-click="cancel-upload" phx-value-ref={entry.ref}>cancel</button>
<% end %>

def handle_event("cancel-upload", %{"ref" => ref}, socket) do
  {:noreply, cancel_upload(socket, :avatar, ref)}
end

清除闪存。

示例

iex> clear_flash(socket)
链接到此函数

clear_flash(socket, key)

查看源代码

从闪存中清除一个键。

示例

iex> clear_flash(socket, :info)

如果 socket 已连接,则返回 true。

用于在挂载视图时检查连接状态。例如,在初始页面渲染时,视图以静态方式挂载,渲染,并将 HTML 发送到客户端。一旦客户端连接到服务器,LiveView 就会在进程中以有状态的方式生成并挂载。使用 connected?/1 有条件地执行有状态的操作,例如订阅发布订阅主题、发送消息等。

示例

defmodule DemoWeb.ClockLive do
  use Phoenix.LiveView
  ...
  def mount(_params, _session, socket) do
    if connected?(socket), do: :timer.send_interval(1000, self(), :tick)

    {:ok, assign(socket, date: :calendar.local_time())}
  end

  def handle_info(:tick, socket) do
    {:noreply, assign(socket, date: :calendar.local_time())}
  end
end
链接到此函数

consume_uploaded_entries(socket, name, func)

查看源代码

使用上传的条目。

当仍有条目正在进行时,会引发异常。通常在提交表单时调用,以处理上传的条目以及表单数据。对于表单提交,保证在调用提交事件之前,所有条目都已完成。一旦条目被使用,它们就会从上传中删除。

传递给使用的函数可以返回形式为 {:ok, my_result} 的带标签元组来收集有关使用条目的结果,或者返回 {:postpone, my_result} 来收集结果,但推迟文件使用,以便稍后执行。

示例

def handle_event("save", _params, socket) do
  uploaded_files =
    consume_uploaded_entries(socket, :avatar, fn %{path: path}, _entry ->
      dest = Path.join("priv/static/uploads", Path.basename(path))
      File.cp!(path, dest)
      {:ok, ~p"/uploads/#{Path.basename(dest)}"}
    end)
  {:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))}
end
链接到此函数

consume_uploaded_entry(socket, entry, func)

查看源代码

使用单个上传的条目。

当条目仍在进行时,会引发异常。通常在提交表单时调用,以处理上传的条目以及表单数据。一旦条目被使用,它们就会从上传中删除。

这是一个比 consume_uploaded_entries/3 更低级的功能,适用于您希望在单个条目完成时使用它们的场景。

consume_uploaded_entries/3 一样,传递给使用的函数可以返回形式为 {:ok, my_result} 的带标签元组来收集有关使用条目的结果,或者返回 {:postpone, my_result} 来收集结果,但推迟文件使用,以便稍后执行。

示例

def handle_event("save", _params, socket) do
  case uploaded_entries(socket, :avatar) do
    {[_|_] = entries, []} ->
      uploaded_files = for entry <- entries do
        consume_uploaded_entry(socket, entry, fn %{path: path} ->
          dest = Path.join("priv/static/uploads", Path.basename(path))
          File.cp!(path, dest)
          {:ok, ~p"/uploads/#{Path.basename(dest)}"}
        end)
      end
      {:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))}

    _ ->
      {:noreply, socket}
  end
end
链接到此函数

detach_hook(socket, name, stage)

查看源代码

从生命周期 stage 中分离具有给定 name 的钩子。

注意:此函数用于服务器端生命周期回调。对于客户端挂钩,请参阅 JS 交互指南

如果没有找到挂钩,此函数将不执行任何操作。

示例

def handle_event(_, socket) do
  {:noreply, detach_hook(socket, :hook_that_was_attached, :handle_event)}
end
链接到此函数

disallow_upload(socket, name)

查看源代码

撤销先前从 allow_upload/3 允许的上传。

示例

disallow_upload(socket, :avatar)
链接到此函数

get_connect_info(socket, key)

查看源代码

访问 socket 中给定的连接信息键。

支持以下键::peer_data:trace_context_headers:x_headers:uri:user_agent

连接信息仅在挂载期间可用。在断开连接的渲染期间,所有键都可用。在连接的渲染中,仅套接字中显式声明的键可用。有关键的完整描述,请参阅 Phoenix.Endpoint.socket/3

示例

第一步是声明要接收的 connect_info。通常,它至少包含会话,但您必须包含要在连接挂载时访问的所有其他键,例如 :peer_data

socket "/live", Phoenix.LiveView.Socket,
  websocket: [connect_info: [:peer_data, session: @session_options]]

现在,这些值可以在连接挂载时作为 get_connect_info/2 访问。

def mount(_params, _session, socket) do
  peer_data = get_connect_info(socket, :peer_data)
  {:ok, assign(socket, ip: peer_data.address)}
end

如果键不可用,通常是因为它没有在 connect_info 中指定,它将返回 nil。

链接到此函数

get_connect_params(socket)

查看源代码

访问客户端发送的连接参数,以便在连接装载时使用。

连接参数仅在客户端连接到服务器时发送,并且仅在挂载期间可用。在断开连接的状态下调用时返回 nil,如果在挂载后调用,则会引发 RuntimeError

保留参数

以下参数在 LiveView 中具有特殊含义

  • "_csrf_token" - CSRF 令牌,用户在连接时必须显式设置
  • "_mounts" - 当前 LiveView 挂载的次数。第一次挂载时为 0,然后在每次重新连接时都会增加。在导航离开当前 LiveView 或出现错误时重置
  • "_track_static" - 自动设置,包含所有带有 phx-track-static 注释的标签中的所有 href/src 的列表。如果没有这样的标签,则不会发送任何内容
  • "_live_referer" - 由客户端发送,作为从 push_navigate 或客户端链接导航发生的实时导航的引用 URL。

示例

def mount(_params, _session, socket) do
  {:ok, assign(socket, width: get_connect_params(socket)["width"] || @width)}
end
链接到此宏

on_mount(mod_or_mod_arg)

查看源代码 (宏)

声明一个模块回调,在 LiveView 装载时调用。

给定模块中的函数(必须命名为 on_mount)将在断开连接和连接的挂载之前调用。挂钩可以选择停止或继续挂载过程。如果您希望重定向 LiveView,则*必须* 停止,否则会引发错误。

提示:如果您需要定义多个 on_mount 回调,请避免定义多个模块。相反,请传递一个元组并使用模式匹配来处理不同的情况

def on_mount(:admin, _params, _session, socket) do
  {:cont, socket}
end

def on_mount(:user, _params, _session, socket) do
  {:cont, socket}
end

然后调用它作为

on_mount {MyAppWeb.SomeHook, :admin}
on_mount {MyAppWeb.SomeHook, :user}

注册 on_mount 挂钩对于执行身份验证以及通过 attach_hook/4 向其他回调添加自定义行为非常有用。

on_mount 回调可以返回一个关键字列表作为返回元组中的第三个元素。这些选项与 mount/3 中可以可选返回的选项相同。

示例

以下是如何通过 Phoenix.LiveView.Router.live_session/3 附加挂钩的示例

# lib/my_app_web/live/init_assigns.ex
defmodule MyAppWeb.InitAssigns do
  @moduledoc """
  Ensures common `assigns` are applied to all LiveViews attaching this hook.
  """
  import Phoenix.LiveView
  import Phoenix.Component

  def on_mount(:default, _params, _session, socket) do
    {:cont, assign(socket, :page_title, "DemoWeb")}
  end

  def on_mount(:user, params, session, socket) do
    # code
  end

  def on_mount(:admin, _params, _session, socket) do
    {:cont, socket, layout: {DemoWeb.Layouts, :admin}}
  end
end

# lib/my_app_web/router.ex
defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  # pipelines, plugs, etc.

  live_session :default, on_mount: MyAppWeb.InitAssigns do
    scope "/", MyAppWeb do
      pipe_through :browser
      live "/", PageLive, :index
    end
  end

  live_session :authenticated, on_mount: {MyAppWeb.InitAssigns, :user} do
    scope "/", MyAppWeb do
      pipe_through [:browser, :require_user]
      live "/profile", UserLive.Profile, :index
    end
  end

  live_session :admins, on_mount: {MyAppWeb.InitAssigns, :admin} do
    scope "/admin", MyAppWeb.Admin do
      pipe_through [:browser, :require_user, :require_admin]
      live "/", AdminLive.Index, :index
    end
  end
end
链接到此函数

push_event(socket, event, payload)

查看源代码

将事件推送到客户端。

事件可以通过两种方式处理

  1. 它们可以通过 addEventListenerwindow 上处理。事件名称将添加 "phx:" 前缀。

  2. 它们可以通过 handleEvent 在挂钩中处理。

事件会被分派到客户端上处理给定 event 的所有活动挂钩。如果您需要对事件进行范围限定,则必须通过对其进行命名空间来完成。

push_navigate 期间推送的事件当前会被丢弃,因为 LiveView 会立即卸载。

挂钩示例

如果您从 LiveView 推送一个 "scores" 事件

{:noreply, push_event(socket, "scores", %{points: 100, user: "josé"})}

通过 phx-hook 声明的挂钩可以通过 handleEvent 来处理它

this.handleEvent("scores", data => ...)

window 示例

所有事件也会在 window 上分派。这意味着您可以通过添加侦听器来处理它们。例如,如果您想从页面中删除一个元素,您可以这样做

{:noreply, push_event(socket, "remove-el", %{id: "foo-bar"})}

现在,在您的 app.js 中,您可以注册并处理它

window.addEventListener(
  "phx:remove-el",
  e => document.getElementById(e.detail.id).remove()
)
链接到此函数

push_navigate(socket, opts)

查看源代码

注释 socket 以导航到另一个 LiveView。

当前的 LiveView 将会被关闭,并用一个新的 LiveView 替换它,而不会重新加载整个页面。这也可用于重新挂载相同的 LiveView,以防您想重新开始。如果您想导航到同一个 LiveView 但不重新挂载它,请使用 push_patch/2

选项

  • :to - 要链接到的必需路径。它必须始终是本地路径。
  • :replace - 用于替换当前历史记录或推送新状态的标志。默认值为 false

示例

{:noreply, push_navigate(socket, to: "/")}
{:noreply, push_navigate(socket, to: "/", replace: true)}
链接到此函数

push_patch(socket, opts)

查看源代码

注释 socket 以在当前 LiveView 中进行导航。

当导航到当前的 LiveView 时,handle_params/3 会立即被调用以处理参数和 URL 状态的变化。然后,新的状态会被推送到客户端,而不会重新加载整个页面,同时也会保持当前的滚动位置。对于到另一个 LiveView 的实时导航,请使用 push_navigate/2

选项

  • :to - 要链接到的必需路径。它必须始终是本地路径。
  • :replace - 用于替换当前历史记录或推送新状态的标志。默认值为 false

示例

{:noreply, push_patch(socket, to: "/")}
{:noreply, push_patch(socket, to: "/", replace: true)}
链接到此函数

put_flash(socket, kind, msg)

查看源代码

将闪存消息添加到 socket 以便显示。

注意:虽然您可以在 Phoenix.LiveComponent 中使用 put_flash/3,但组件有自己的 @flash 赋值。组件中的 @flash 赋值只有在组件调用 push_navigate/2push_patch/2 时才会复制到其父 LiveView。

注意:您还必须在浏览器的管道中将 Phoenix.LiveView.Router.fetch_live_flash/2 插件放在 fetch_flash 的位置,以便支持 LiveView 闪存消息,例如

import Phoenix.LiveView.Router

pipeline :browser do
  ...
  plug :fetch_live_flash
end

示例

iex> put_flash(socket, :info, "It worked!")
iex> put_flash(socket, :error, "You can't access that page")
链接到此函数

put_private(socket, key, value)

查看源代码

在 socket 中放置一个新的私有键和值。

私有数据不会被跟踪变化。此存储旨在供用户和库使用,以保存不需要变化跟踪的状态。键应该以应用程序/库名称为前缀。

示例

键值可以放在私有数据中

put_private(socket, :myapp_meta, %{foo: "bar"})

然后检索

socket.private[:myapp_meta]
链接到此函数

redirect(socket, opts \\ [])

查看源代码

注释 socket 以重定向到目标路径。

注意:LiveView 重定向依赖于指示客户端对提供的重定向位置执行 window.location 更新。整个页面将被重新加载,所有状态将被丢弃。

选项

  • :to - 要重定向到的路径。它必须始终是本地路径。
  • :external - 要重定向到的外部路径。可以是字符串,也可以是 {scheme, url},用于重定向到自定义方案。

示例

{:noreply, redirect(socket, to: "/")}
{:noreply, redirect(socket, external: "https://example.com")}
链接到此函数

render_with(socket, component)

查看源代码

配置用于渲染 LiveView/LiveComponent 的函数。

默认情况下,LiveView 会在 LiveView/LiveComponent 定义的同一个模块中调用 render/1 函数,并以 assigns 作为其唯一参数。此函数允许您设置不同的渲染函数。

此函数的一种可能的用例是在断开连接的渲染时设置不同的模板。当用户首次访问 LiveView 时,我们将执行断开连接的渲染以发送到浏览器。这对于几个原因非常有用,例如减少首次绘制时间以及用于搜索引擎索引。

但是,当 LiveView 被限制在身份验证页面之后时,在断开连接的渲染时渲染占位符,并在 WebSocket 连接后执行完整渲染可能很有用。这可以通过 render_with/2 来实现,并且在复杂页面(如仪表板和报表)上特别有用。

为此,您只需调用 render_with(socket, &some_function_component/1),并使用新的渲染函数配置您的套接字。

链接到此函数

send_update(pid \\ self(), module_or_cid, assigns)

查看源代码

异步更新 Phoenix.LiveComponent,并使用新的赋值。

pid 参数是可选的,它默认为当前进程,这意味着更新指令将被发送到在同一个 LiveView 上运行的组件。如果当前进程不是 LiveView,或者您想将更新发送到在另一个 LiveView 上运行的实时组件,您应该显式地传递 LiveView 的 pid。

第二个参数可以是 @myself 的值或实时组件的模块。如果您传递了模块,那么用于标识组件的 :id 必须作为赋值的一部分传递。

当组件收到更新时,如果定义了 update_many/1,它将被调用,否则 update/2 将使用新的赋值被调用。如果 update/2 未定义,所有赋值都将简单地合并到套接字中。作为 update/2 回调的第一个参数接收的赋值将只包含从该函数传递的新的赋值。可以在 socket.assigns 中找到现有的赋值。

虽然组件可以通过更新一些父赋值来始终从父级更新,这将重新渲染子组件,从而在子组件上调用 update/2,但 send_update/3 有助于更新完全管理自身状态的组件,以及在同一个 LiveView 中挂载的组件之间的消息传递。

示例

def handle_event("cancel-order", _, socket) do
  ...
  send_update(Cart, id: "cart", status: "cancelled")
  {:noreply, socket}
end

def handle_event("cancel-order-asynchronously", _, socket) do
  ...
  pid = self()

  Task.Supervisor.start_child(MyTaskSup, fn ->
    # Do something asynchronously
    send_update(pid, Cart, id: "cart", status: "cancelled")
  end)

  {:noreply, socket}
end

def render(assigns) do
  ~H"""
  <.some_component on_complete={&send_update(@myself, completed: &1)} />
  """
end
链接到此函数

send_update_after(pid \\ self(), module_or_cid, assigns, time_in_milliseconds)

查看源代码

send_update/3 类似,但更新将根据给定的 time_in_milliseconds 延迟。

它返回一个引用,可以使用 Process.cancel_timer/1 取消。

示例

def handle_event("cancel-order", _, socket) do
  ...
  send_update_after(Cart, [id: "cart", status: "cancelled"], 3000)
  {:noreply, socket}
end

def handle_event("cancel-order-asynchronously", _, socket) do
  ...
  pid = self()

  Task.start(fn ->
    # Do something asynchronously
    send_update_after(pid, Cart, [id: "cart", status: "cancelled"], 3000)
  end)

  {:noreply, socket}
end
链接到此宏

start_async(socket, name, func, opts \\ [])

View Source (宏)

将您的函数包装在一个异步任务中,并调用一个回调 name 来处理结果。

任务与调用者链接,错误/退出被包装。任务的结果将被发送到调用者 LiveView 或 LiveComponent 的 handle_async/3 回调。

该任务只有在 socket 连接时才会开始。

选项

示例

def mount(%{"id" => id}, _, socket) do
  {:ok,
   socket
   |> assign(:org, AsyncResult.loading())
   |> start_async(:my_task, fn -> fetch_org!(id) end)}
end

def handle_async(:my_task, {:ok, fetched_org}, socket) do
  %{org: org} = socket.assigns
  {:noreply, assign(socket, :org, AsyncResult.ok(org, fetched_org))}
end

def handle_async(:my_task, {:exit, reason}, socket) do
  %{org: org} = socket.assigns
  {:noreply, assign(socket, :org, AsyncResult.failed(org, {:exit, reason}))}
end

查看模块文档以了解更多信息。

链接到此函数

static_changed?(socket)

查看源代码

如果 socket 已连接且跟踪的静态资产已更改,则返回 true。

此函数用于检测客户端是否运行在标记的静态文件的旧版本上。它通过将客户端发送的静态路径与服务器上的静态路径进行比较来工作。

注意:此功能需要 Phoenix v1.5.2 或更高版本。

要使用此功能,第一步是使用 phx-track-static 注释要由 LiveView 跟踪的静态文件。例如

<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}></script>

现在,每当 LiveView 连接到服务器时,它都会发送所有跟踪的静态文件 srchref 属性的副本,并将这些值与服务器上由 mix phx.digest 计算的最新条目进行比较。

客户端上的跟踪静态文件在绝大多数情况下都会与服务器上的匹配。但是,如果部署了新版本,这些值可能会有所不同。您可以使用此函数来检测这些情况,并向用户显示一个横幅,要求他们重新加载页面。为此,首先在挂载时设置赋值

def mount(params, session, socket) do
  {:ok, assign(socket, static_changed?: static_changed?(socket))}
end

然后在您的视图中

<%= if @static_changed? do %>
  <div id="reload-static">
    The app has been updated. Click here to <a href="#" onclick="window.location.reload()">reload</a>.
  </div>
<% end %>

如果您愿意,您也可以发送一个立即重新加载页面的 JavaScript 脚本。

注意:只在您自己的资产上设置 phx-track-static。例如,不要在外部 JavaScript 文件中设置它。

<script defer phx-track-static type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

因为您实际上并没有提供上面的文件,所以 LiveView 会将上面的静态文件解释为丢失,此函数将返回 true。

链接到此函数

stream(socket, name, items, opts \\ [])

查看源代码
@spec stream(
  %Phoenix.LiveView.Socket{
    assigns: term(),
    endpoint: term(),
    fingerprints: term(),
    host_uri: term(),
    id: term(),
    parent_pid: term(),
    private: term(),
    redirected: term(),
    root_pid: term(),
    router: term(),
    transport_pid: term(),
    view: term()
  },
  name :: atom() | String.t(),
  items :: Enumerable.t(),
  opts :: Keyword.t()
) :: %Phoenix.LiveView.Socket{
  assigns: term(),
  endpoint: term(),
  fingerprints: term(),
  host_uri: term(),
  id: term(),
  parent_pid: term(),
  private: term(),
  redirected: term(),
  root_pid: term(),
  router: term(),
  transport_pid: term(),
  view: term()
}

将一个新流分配给 socket,或将条目插入到现有流中。返回一个更新后的 socket

流是一种在客户端管理大型集合而无需在服务器上保留资源的机制。

  • name - 要放在 @streams 赋值下的键的字符串或原子名称。
  • items - 要插入的项目的枚举。

支持以下选项

  • :at - 在客户端上插入或更新集合中项目的索引。默认情况下使用 -1,它将项目追加到父 DOM 容器。值为 0 将项目添加到开头。

    请注意,此操作等同于逐个插入项目,每个项目都位于给定的索引处。因此,当在除 -1 以外的索引处插入多个项目时,UI 将以相反的顺序显示项目。

    stream(socket, :songs, [song1, song2, song3], at: 0)

    在这种情况下,UI 将首先追加 song1,然后追加 song2,然后追加 song3,因此它将显示 song3song2song1,然后是之前插入的任何项目。

    要按列表顺序插入,请使用 Enum.reverse/1

    stream(socket, :songs, Enum.reverse([song1, song2, song3]), at: 0)
  • :reset - 一个布尔值,用于确定是否在客户端上重置流。默认为 false

  • :limit - 一个可选的正数或负数,用于限制客户端 UI 上的结果数量。当流式传输新项目时,UI 将删除现有项目以保持限制。例如,要将流限制为 UI 中的最后 10 个项目,同时追加新项目,请传递一个负值

    stream(socket, :songs, songs, at: -1, limit: -10)

    同样,要将流限制为前 10 个项目,同时在开头追加新项目,请传递一个正值

    stream(socket, :songs, songs, at: 0, limit: 10)

定义流后,将提供一个新的 @streams 赋值,其中包含已定义流的名称。例如,在上面的定义中,可以将流引用为模板中的 @streams.songs。流项目是临时的,并在调用 render/1 函数(或从磁盘渲染模板)后立即从套接字状态中释放。

默认情况下,对现有流调用 stream/4 将在客户端上批量插入新项目,同时保留现有项目。在调用 stream/4 时,流也可以被重置,我们将在下面讨论。

重置流

要清空客户端上的流容器,您可以将 :reset 与空列表一起传递

stream(socket, :songs, [], reset: true)

或者您可以用新的集合替换客户端上的整个流

stream(socket, :songs, new_songs, reset: true)

限制流

在允许服务器以“即发即弃”的方式流式传输新项目的同时限制 UI 中的项目数量通常很有用。这可以防止服务器用新结果压倒客户端,同时也打开了虚拟化无限滚动等强大的功能。有关带有流限制的完整双向无限滚动示例,请参阅 滚动事件指南

当流在客户端超过限制时,现有项目将根据流容器中的项目数量和限制方向进行修剪。正限制将从容器末尾修剪项目,而负限制将从容器开头修剪项目。

请注意,限制不会在第一次 mount/3 渲染时强制执行(此时尚未建立 WebSocket 连接),因为这意味着已加载了比必要更多的数据。在这种情况下,您只应加载和传递所需数量的项目到流中。

当使用 stream_insert/4 插入单个项目时,需要将限制作为选项传递,以便在客户端强制执行。

stream_insert(socket, :songs, song, limit: -10)

必需的 DOM 属性

为了在客户端跟踪流项目,必须满足以下要求:

  1. 父 DOM 容器必须包含 phx-update="stream" 属性,以及唯一的 DOM ID。
  2. 每个流项目都必须在其项目的元素上包含其 DOM ID。

注意

如果未在每个流的直接父级上放置 phx-update="stream",会导致行为异常。

此外,不要更改生成的 DOM ID,例如,不要在它们前面加前缀。这样做会导致行为异常。

在模板中使用流时,DOM ID 和项目将作为元组传递,允许方便地包含每个项目的 DOM ID。例如:

<table>
  <tbody id="songs" phx-update="stream">
    <tr
      :for={{dom_id, song} <- @streams.songs}
      id={dom_id}
    >
      <td><%= song.title %></td>
      <td><%= song.duration %></td>
    </tr>
  </tbody>
</table>

我们通过引用 @streams.songs 赋值在 for 推导式中使用流。我们使用计算出的 DOM ID 填充 <tr> ID,然后像往常一样渲染表格行。

现在,可以发出 stream_insert/3stream_delete/3,新的行将被插入或从客户端删除。

处理空情况

在渲染项目列表时,通常会显示一条空情况的消息。但是,在使用流时,我们不能依赖于 Enum.empty?/1 或类似方法来检查列表是否为空。相反,我们可以使用 CSS :only-child 选择器并在客户端显示消息。

<table>
  <tbody id="songs" phx-update="stream">
    <tr id="songs-empty" class="only:block hidden">
      <td colspan="2">No songs found</td>
    </tr>
    <tr
      :for={{dom_id, song} <- @streams.songs}
      id={dom_id}
    >
      <td><%= song.title %></td>
      <td><%= song.duration %></td>
    </tr>
  </tbody>
</table>

流容器中的非流项目

在处理空情况的部分,我们展示了如何在流为空时通过在流容器中渲染非流项目来渲染消息。

请注意,对于 phx-update="stream" 容器中的非流项目,需要考虑以下几点:

  1. 项目可以添加和更新,但不能删除,即使流被重置。

这意味着,如果您尝试在流容器中条件渲染非流项目,如果它已渲染过一次,它就不会被删除。

  1. 项目受 :at 选项影响。

例如,当您在流容器的开头渲染非流项目,然后在流中添加项目(使用 at: 0)时,非流项目将被向下推。

链接到此函数

stream_configure(socket, name, opts)

查看源代码
@spec stream_configure(
  %Phoenix.LiveView.Socket{
    assigns: term(),
    endpoint: term(),
    fingerprints: term(),
    host_uri: term(),
    id: term(),
    parent_pid: term(),
    private: term(),
    redirected: term(),
    root_pid: term(),
    router: term(),
    transport_pid: term(),
    view: term()
  },
  name :: atom() | String.t(),
  opts :: Keyword.t()
) :: %Phoenix.LiveView.Socket{
  assigns: term(),
  endpoint: term(),
  fingerprints: term(),
  host_uri: term(),
  id: term(),
  parent_pid: term(),
  private: term(),
  redirected: term(),
  root_pid: term(),
  router: term(),
  transport_pid: term(),
  view: term()
}

配置一个流。

支持以下选项

  • :dom_id - 一个可选函数,用于生成每个流项目的 DOM ID。该函数接受每个流项目,并将该项目转换为字符串 ID。默认情况下,如果项目具有此类字段,则会使用映射或结构的 :id 字段,并将使用 name 与 ID 连字符分隔作为前缀。例如,以下示例等效:

    stream(socket, :songs, songs)
    
    socket
    |> stream_configure(:songs, dom_id: &("songs-#{&1.id}"))
    |> stream(:songs, songs)

必须在插入项目之前配置流,并且一旦配置,流就无法重新配置。为了确保流在 LiveComponent 中只被配置一次,请使用 mount/1 回调。例如:

def mount(socket) do
  {:ok, stream_configure(socket, :songs, dom_id: &("songs-#{&1.id}"))}
end

def update(assigns, socket) do
  {:ok, stream(socket, :songs, ...)}
end

返回一个更新的 socket

链接到此函数

stream_delete(socket, name, item)

查看源代码
@spec stream_delete(
  %Phoenix.LiveView.Socket{
    assigns: term(),
    endpoint: term(),
    fingerprints: term(),
    host_uri: term(),
    id: term(),
    parent_pid: term(),
    private: term(),
    redirected: term(),
    root_pid: term(),
    router: term(),
    transport_pid: term(),
    view: term()
  },
  name :: atom() | String.t(),
  item :: any()
) :: %Phoenix.LiveView.Socket{
  assigns: term(),
  endpoint: term(),
  fingerprints: term(),
  host_uri: term(),
  id: term(),
  parent_pid: term(),
  private: term(),
  redirected: term(),
  root_pid: term(),
  router: term(),
  transport_pid: term(),
  view: term()
}

从流中删除一个项目。

项目的 DOM 是根据 stream/3 定义中提供的 :dom_id 计算得出的。有关此 DOM ID 的删除信息将发送到客户端,并且项目的元素将从 DOM 中删除,遵循元素删除的相同行为,例如调用 phx-remove 命令和执行客户端钩子 destroyed() 回调。

示例

def handle_event("delete", %{"id" => id}, socket) do
  song = get_song!(id)
  {:noreply, stream_delete(socket, :songs, song)}
end

请参阅 stream_delete_by_dom_id/3 以删除项目,而无需原始数据结构。

返回一个更新的 socket

链接到此函数

stream_delete_by_dom_id(socket, name, id)

查看源代码
@spec stream_delete_by_dom_id(
  %Phoenix.LiveView.Socket{
    assigns: term(),
    endpoint: term(),
    fingerprints: term(),
    host_uri: term(),
    id: term(),
    parent_pid: term(),
    private: term(),
    redirected: term(),
    root_pid: term(),
    router: term(),
    transport_pid: term(),
    view: term()
  },
  name :: atom() | String.t(),
  id :: String.t()
) :: %Phoenix.LiveView.Socket{
  assigns: term(),
  endpoint: term(),
  fingerprints: term(),
  host_uri: term(),
  id: term(),
  parent_pid: term(),
  private: term(),
  redirected: term(),
  root_pid: term(),
  router: term(),
  transport_pid: term(),
  view: term()
}

根据计算后的 DOM ID 从流中删除一个项目。

返回一个更新的 socket

行为与 stream_delete/3 相同,但接受预先计算的 DOM ID,这允许从流中删除项目,而无需获取或构建原始流数据结构。

示例

def render(assigns) do
  ~H"""
  <table>
    <tbody id="songs" phx-update="stream">
      <tr
        :for={{dom_id, song} <- @streams.songs}
        id={dom_id}
      >
        <td><%= song.title %></td>
        <td><button phx-click={JS.push("delete", value: %{id: dom_id})}>delete</button></td>
      </tr>
    </tbody>
  </table>
  """
end

def handle_event("delete", %{"id" => dom_id}, socket) do
  {:noreply, stream_delete_by_dom_id(socket, :songs, dom_id)}
end
链接到此函数

stream_insert(socket, name, item, opts \\ [])

查看源代码
@spec stream_insert(
  %Phoenix.LiveView.Socket{
    assigns: term(),
    endpoint: term(),
    fingerprints: term(),
    host_uri: term(),
    id: term(),
    parent_pid: term(),
    private: term(),
    redirected: term(),
    root_pid: term(),
    router: term(),
    transport_pid: term(),
    view: term()
  },
  name :: atom() | String.t(),
  item :: any(),
  opts :: Keyword.t()
) :: %Phoenix.LiveView.Socket{
  assigns: term(),
  endpoint: term(),
  fingerprints: term(),
  host_uri: term(),
  id: term(),
  parent_pid: term(),
  private: term(),
  redirected: term(),
  root_pid: term(),
  router: term(),
  transport_pid: term(),
  view: term()
}

在流中插入一个新项目或更新一个现有项目。

返回一个更新的 socket

请参阅 stream/4 以了解如何一次插入多个项目。

支持以下选项

  • :at - 在客户端将项目插入或更新到集合中的索引。默认情况下,项目将附加到父 DOM 容器。这与传递限制 -1 相同。如果项目已存在于父 DOM 容器中,则它将在原位更新。

  • :limit - 在 UI 中保持的项目的限制。传递给 stream/4 的限制不会影响后续对 stream_insert/4 的调用,因此限制必须在这里也传递,以便强制执行。有关限制流的更多信息,请参阅 stream/4

示例

假设您在挂载时定义了一个包含单个项目的流:

stream(socket, :songs, [%Song{id: 1, title: "Song 1"}])

然后,在回调(例如 handle_infohandle_event)中,您可以追加新歌曲:

stream_insert(socket, :songs, %Song{id: 2, title: "Song 2"})

或使用 at: 0 添加新歌曲:

stream_insert(socket, :songs, %Song{id: 2, title: "Song 2"}, at: 0)

或更新现有歌曲(在这种情况下,:at 选项没有影响):

stream_insert(socket, :songs, %Song{id: 1, title: "Song 1 updated"}, at: 0)

或在将流限制为最后 10 个项目时追加新歌曲:

stream_insert(socket, :songs, %Song{id: 2, title: "Song 2"}, limit: -10)

更新项目

如所示,可以通过对现有项目发出 stream_insert 来更新客户端上的现有项目。当客户端更新现有项目时,该项目将保留在其先前的位置,并且不会移动到父项的末尾。为了同时更新现有项目并将其移动到另一个位置,请发出 stream_delete,然后发出 stream_insert。例如:

song = get_song!(id)

socket
|> stream_delete(:songs, song)
|> stream_insert(:songs, song, at: -1)

请参阅 stream_delete/3 以了解有关删除项目的更多信息。

返回套接字的传输 PID。

如果套接字未连接,则引发 ArgumentError

示例

iex> transport_pid(socket)
#PID<0.107.0>
链接到此函数

uploaded_entries(socket, name)

查看源代码

返回上传已完成和正在进行的条目。

示例

case uploaded_entries(socket, :photos) do
  {[_ | _] = completed, []} ->
    # all entries are completed

  {[], [_ | _] = in_progress} ->
    # all entries are still in progress
end