檢視原始碼 Phoenix.LiveView 行為 (Phoenix LiveView v0.20.17)

LiveView 是一個接收事件、更新其狀態並以 diffs 方式將更新呈現在頁面上的處理序。

開始之前,請參閱 歡迎指南。這個模組提供進階的文件和關於使用 LiveView 的功能。

生命週期

LiveView 從一般的 HTTP 要求和 HTML 回應開始,然後在用戶端連線後升級為一個有狀態檢視,確保即使 JavaScript 已停用也能產生一般的 HTML 頁面。任何時候只要有狀態檢視變更或更新其 socket 指派,它就會自動重新呈視,並將更新傳遞給用戶端。

Socket 指派是有狀態值,儲存在 Phoenix.LiveView.Socket 的伺服器端。這與常見的無狀態 HTTP 模式不同,HTTP 模式是以令牌或 cookie 的形式將連線狀態傳送給用戶端,並在伺服器上重新建構該狀態以提供每個要求的服務。

你通常會從路由器開始呈視 LiveView。當 LiveView 第一次被呈視時,mount/3 回呼會使用目前的 params、目前的 session 及 LiveView socket 被呼叫。就像在一般的要求處理中,params 包含公眾資料,可讓使用者修改。而 session 永遠包含應用程式自己設定的私人資料。mount/3 回呼將 socket 指派連接起來,以便呈視檢視。掛載後,handle_params/3 會被呼叫,這樣 uri 和查詢 params 就能被處理了。最後,render/1 會被呼叫,而 HTML 則會傳送成一般的 HTML 回應給用戶端。

在呈現靜態頁面後,LiveView 從 Client 端連線到 Server 端,而 Stateful Views 會在 Server 端衍生,以便將呈現的更新推播至瀏覽器,並透過 phx- 繫結來接收 Client 端事件。如同第一次呈現,會以 params、session 和 Socket 狀態呼叫 mount/3。但在已連線的 Client 案例中,LiveView 程序會在 Server 端衍生,再次執行 handle_params/3,然後將 render/1 的結果推播至 Client 端,並持續連線。如果在狀態生命週期的任何時間點遇到異常終止或 Client 端連線中斷,Client 端會重新連線至 Server 端,然後再次呼叫 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 中。

非同步作業

在 LiveViews 和 LiveComponents 中執行非同步作業很常見。這樣一來,用戶可以快速取得運作良好的 UI,而系統則會在背景中擷取一些資料或與外部服務互動,不會阻擋呈現或事件處理。對於非同步作業,您通常還需要處理非同步作業的不同狀態,例如加載、錯誤和處理結果。您還想要捕捉任何錯誤或退出並將其轉譯為 UI 中有意義的更新,而不是讓用戶體驗異常終止。

非同步指定

assign_async/3 函數帶有 Socket、非同步指定的 key 或 key 清單,以及函式。此函數會封裝在 task 中,讓您可以輕易傳回結果。此函式必須傳回 {:ok, assigns}{:error, reason} 樹狀結構,其中 assigns 是傳遞給 assign_async 的 key 清單。如果該函式傳回其他任何項目,則會引發錯誤。

只有當 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 結構到工作程序,這會非常昂貴。

替代

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.dev.org.tw/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,如果你想要自己處理狀態。

端點設定

:live_view 鍵下的端點,LiveView 接受下列設定

  • :signing_salt(必要)- 用於簽署傳送給用戶端資料的鹽

  • :hibernate_after(可選)- 在 LiveView 中允許壓縮它自身的記憶體和狀態的閒置時間(毫秒)。預設為 15000 毫秒(15 秒)

摘要

回呼

start_async/3 作業的結果可用時呼叫。

呼叫其他 Elixir 程序時觸發。

其他 Elixir 程序觸發時呼叫。

用戶端傳送事件時觸發。

其他 Elixir 程序傳送訊息時觸發。

建立掛載後呼叫,以及每次有現場修補事件時。

LiveView 進入點。

呈現範本。

在 LiveView 終止時呼叫。

函數

定義 LiveView 的後設資料。

在當前模組中使用 LiveView 做為 LiveView 標記。

允許上傳提供的名稱。

將給定的 fun 透過 stage 中的生命週期 name 附加到 stage 中。

如果存在非同步作業,則取消。

取消給定項目上傳作業。

清除快閃訊息。

從快閃訊息中清除金鑰。

如果套接字連線,則回傳 true。

消耗單一上傳的項目。

將名稱為 name 的鉤子從生命週期 stage 中分離。

撤銷 allow_upload/3 先前允許上傳的項目。

從套接字中取得給定的連線資訊金鑰。

取得用戶端傳送用於連線掛載的連線參數。

宣告模組回呼,以便在 LiveView 掛載時呼叫。

將事件推送到用戶端。

註解套接字,以便移動到另一個 LiveView。

註解套接字,以便在當前 LiveView 內移動。

新增快閃訊息至套接字,在顯示後將移除。

將新的私人金鑰與值放置在套接字中。

註解套接字,以便重新導向至目標路徑。

設定使用何種函數來呈現 LiveView/LiveComponent。

send_update/3 相似,但更新會根據指定的 time_in_milliseconds 延遲。

將你的函式包裝成非同步任務並呼叫處理結果的名稱為 name 的回呼。

如果 socket 處於連線狀態且追蹤的靜態資源已變更,則傳回 true。

指派新的串流至 socket 或將目前的串流加入項目。傳回已更新的 socket

從串流刪除項目。

根據已計算的 DOM id 從串流中刪除項目。

插入新的項目或更新串流中現有的項目。

傳回 socket 的 transport pid。

傳回上傳已完成及進行中的項目。

類型

@type unsigned_params() :: map()

回呼

連結至此回呼

handle_async(name, async_fun_result, socket)

檢視原始程式碼 (optional)
@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)

檢視原始程式碼 (optional)
@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)

檢視原始程式碼 (optional)
@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 名稱、事件酬載 (payload) (以映射表示) 及 Socket。

必須傳回 {: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,包含路由器參數、用戶端的 urisocket。在裝載完畢或發生由 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 會呼叫兩次:一次進行初始的頁面載入,另一次則建立動態 Socket。

它有三項引數

  • params - 一個字串金鑰的映射,其中包含可以由使用者設定的公開資訊。此映射包含查詢參數以及任何路由器路徑參數。如果 LiveView 未在路由器中裝載,此引數會為原子 :not_mounted_at_router
  • session - 連線階段
  • socket - LiveView Socket

它必須傳回 {:ok, socket}{:ok, socket, options},其中 options 為下列之一

  • :temporary_assigns - 一個關鍵字清單,包含暫時的 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 相同的目錄下呈現範本。例如,如果您有一個 LiveView 名稱為 MyApp.MyCustomView 位於 lib/my_app/live_views/my_custom_view.ex 內,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 - 一個 HTML 標籤和 DOM 屬性所需的選用元組,用於 LiveView 容器。例如:{: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 - 一個二元函數,用於為外部用戶端上傳器產生元資料。此函數必須回傳 {:ok, meta, socket}{:error, meta, socket},其中 meta 為一個映射。請參閱上傳區段的範例使用方式。

  • :progress - 一個接收進度事件的選用三元函數。

  • :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)

檢視原始程式碼

將給定的 fun 透過 stage 中的生命週期 name 附加到 stage 中。

注意:此函數用於伺服器端的生命週期回呼。若要了解用戶端掛勾,請參閱 JS Interop 指南

掛勾提供機制,以便在必要時點入 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 本身必須傳回。任何更新至 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)

從快閃訊息中清除金鑰。

範例

iex> clear_flash(socket, :info)

如果套接字連線,則回傳 true。

用於在掛載檢視時檢查連線狀態。例如,在初始頁面渲染時,檢視會以靜態方式掛載、渲染,然後 HTML 傳送到用戶端。一旦客戶端連線到伺服器,就會衍生一個 LiveView,並在程序中以有狀態的方式掛載。使用 connected?/1 來有條件地執行有狀態的工作,例如訂閱 pubsub 主題、傳送訊息等。

範例

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)

檢視原始程式碼

消耗上傳的項目。

當仍有條目進行中時觸發。通常在提交表單時呼叫,以在表單資料中處理上傳的條目。對於表單提交,保證在呼叫提交事件之前,所有條目都已完成。一旦條目被使用,它們就會從上傳中移除。

傳遞給 consume 的函式可能會傳回形式 {: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 一樣,傳遞給 consume 的函式可能會傳回形式 {: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)

檢視原始程式碼

將名稱為 name 的鉤子從生命週期 stage 中分離。

注意:此函數用於伺服器端的生命週期回呼。若要了解用戶端掛勾,請參閱 JS Interop 指南

如果沒有找到掛鉤,這個函式是不會運作的。

範例

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)

檢視原始程式碼

從套接字中取得給定的連線資訊金鑰。

支援下列金鑰: :peer_data:trace_context_headers:x_headers:uri:user_agent

連線資訊只在掛載期間可用。在中斷渲染期間,所有金鑰都可用。在連線渲染期間,只有在 socket 中明確宣告的金鑰可用。請參閱 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。

取得用戶端傳送用於連線掛載的連線參數。

連線參數只會在用戶端連線至伺服器時傳送,而且只在掛載期間可用。當在未連線狀態中呼叫時會傳回 nil,而在掛載之後呼叫則會引發 RuntimeError

保留參數

以下參數在 LiveView 中具有特殊含義

  • "_csrf_token" - 用戶連線時必須明確設定的 CSRF Token
  • "_mounts" - 目前 LiveView 已掛載的次數。第一次掛載時為 0,然後在每次重新連線時增加。在導覽離開目前的 LiveView 或發生錯誤時重置
  • "_track_static" - 自動設定為具備 phx-track-static 標註之所有標籤所述的 href/src 清單。如果沒有此類標籤,則不傳送任何內容
  • "_live_referer" - 當從 push_navigate 或用戶端連結導覽發生即時導覽時,由用戶端傳送為 referrer 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 附加一個 hook

# 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. 他們可以在 window 上透過 addEventListener 處理。一個 "phx:" 前綴會加入到事件名稱中。

  2. 他們可以在一個 hook 中透過 handleEvent 處理。

事件被派送給客戶端上所有處理給定 event 的 active hook。如果您需要限定事件範圍,則必須透過命名空間來完成。

push_navigate 期間推出的事件目前會被捨棄,因為 LiveView 會立即卸載。

hook 範例

如果您從 LiveView 推出一個 "scores" 事件

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

一個透過 phx-hook 宣告的 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()
)

註解套接字,以便移動到另一個 LiveView。

目前的 LiveView 將會關閉而且一個新的將會掛載到它的位置上,而不會重新載入整個頁面。您也可以使用它來重新載入同一個 LiveView,以防您想要一個全新的開始。如果您想要導航到同一個 LiveView 而不需要重新掛載它,請改用 push_patch/2

選項

  • :to - 必要,要連結到的路徑。它必須永遠都是一個區域路徑
  • :replace - 取代目前歷史記錄或推動一個新狀態的旗標。預設 false

範例

{:noreply, push_navigate(socket, to: "/")}
{:noreply, push_navigate(socket, to: "/", replace: true)}

註解套接字,以便在當前 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)

檢視原始程式碼

新增快閃訊息至套接字,在顯示後將移除。

注意:雖然可以在 put_flash/3 內部使用 Phoenix.LiveComponent,但組件有自己的 @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)

檢視原始程式碼

將新的私人金鑰與值放置在套接字中。

私密並不會追蹤變更。此儲存預期由使用者和程式庫使用,用於存放不需要追蹤變更的狀態。金鑰應以 app/library 名稱作為開頭。

範例

金鑰值可以放入私密中

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

然後再擷取

socket.private[:myapp_meta]
連結至這個函數

redirect(socket, opts \\ [])

檢視原始程式碼

註解套接字,以便重新導向至目標路徑。

注意: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),使用新的儲存函式組態您的 Socket。

連結至這個函數

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

檢視原始程式碼

非同步更新 Phoenix.LiveComponent 以獲得新指派。

pid 參數是選配的而且預設為目前的處理程序,這表示更新指示會發送給執行在同一個 LiveView 上的組件。如果目前的處理程序不是 LiveView 或你想發送更新給執行在另一個 LiveView 上的 Live 組件,你應明確傳遞 LiveView 的 pid。

第二個參數可以是 @myself 的值或 Live 組件的模組。如果你傳遞模組,然後用於識別組件的 :id 必須作為 assigns 的一部分傳遞。

當組件收到更新,update_many/1 會被呼叫,如果它已定義,否則 update/2 會被呼叫新的 assigns。如果 update/2 未定義,所有 assigns 都會簡單地合併進 Socket 中。接收為 update/2 回呼函式的第一個參數的 assigns 僅包括從此函式傳遞的 assigns。現有的 assigns 可以在 socket.assigns 中找到。

儘管組件可能總是透過更新一些父項 assigns 而由父項更新,這會重新呈現子項,進而呼叫子項組件上的 update/2send_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 \\ [])

查看來源 (巨集)

將你的函式包裝成非同步任務並呼叫處理結果的名稱為 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

更多資訊,請見模組文件。

如果 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 計算出的最新項目進行比較。

用戶端的追蹤靜態檔案大部分情況下都會與伺服器上的檔案相符。然而,如果有新的部署,這些值可能會不同。你可以使用這項功能偵測這些情況,並向使用者顯示橫幅,要求他們重新載入這個網頁。若要這麼做,請先設定 mount 時的指派

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 以外的索引處插入多個項目時,使用者介面將以相反順序顯示項目

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

    在此情況下,使用者介面會先加上 song1,然後是 song2,最後是 song3,因此它會顯示 song3song2song1,然後是之前插入的任何項目。

    若要依照清單順序插入,請使用 Enum.reverse/1

    stream(socket, :songs, Enum.reverse([song1, song2, song3]), at: 0)
  • :reset - 布林值,決定是否在客戶端上重設串流。預設為 false

  • :limit - 在客戶端的使用者介面上限制結果的正數或負數。隨著串流新項目,使用者介面會移除現有項目以維持限制。例如,若要在附加新項目的同時將串流限制在使用者介面的最後 10 個項目,請傳遞負值

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

    同樣地,若要在附加新項目的同時將串流限制在前 10 個項目,請傳遞正值

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

一旦串流被定義,新的 @streams 指派可用,包含已定義串流的名稱。例如,在以上的定義中,串流可以用 @streams.songs 在範本中參考。串流項目是暫時的,在 render/1 函式被啟動 (或從磁碟呈現範本)之後馬上從 socket 狀態中釋放。

預設,呼叫 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 comprehension 中使用串流。我們使用已計算的 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 - 使用者介面中維護項目的限制。傳遞給 stream/4 的限制不會影響 stream_insert/4 的後續呼叫,因此必須在此處也傳遞該限制,才能強制執行。請參閱 stream/4 以瞭解有關限制串流的更多資訊。

範例

假設您在 mount 上定義一個具有單一項目的串流

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 以瞭解有關刪除項目的更多資訊。

傳回 socket 的 transport 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