檢視原始碼 JavaScript 互動性
若要啟用 LiveView 客戶端/伺服器互動,我們使用 LiveSocket。舉例來說
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()
所有選項都直接傳遞給 Phoenix.Socket
建構函式,但下列選項是特定於 LiveView
bindingPrefix
- 要用於 Phoenix 繫結的前置字。預設值"phx-"
params
- 要傳遞給檢視 mount 回呼的connect_params
。可能是文字物件或傳回物件的閉包。當提供閉包時,這個函式會接收檢視的元素。hooks
– 對使用者定義的 hooks 名稱空間的參照,其中包含伺服器/客戶端交互操作的客戶端回呼。有關詳細資訊,請參閱下方的 客戶端 hooks 區段。uploaders
– 對使用者定義的 uploaders 名稱空間的參照,其中包含客戶端側,直接上傳到雲端的客戶端回呼。有關詳細資訊,請參閱 外部上傳指南
偵錯客戶端事件
為了解決問題時幫助客戶端進行偵錯,enableDebug()
和 disableDebug()
函式會顯示在 LiveSocket
JavaScript 執行個體上。呼叫 enableDebug()
會開啟偵錯記錄,其中包括當 LiveView 生命週期和酬載事件在客戶端與伺服器之間往返時。在實務上,您可以將執行個體顯示在 window
上,以直接在瀏覽器的網頁控制台存取,例如
// app.js
let liveSocket = new LiveSocket(...)
liveSocket.connect()
window.liveSocket = liveSocket
// in the browser's web console
>> liveSocket.enableDebug()
偵錯狀態使用瀏覽器內建的 sessionStorage
,因此它會在瀏覽器作業階段持續有效。
模擬延遲
適當處理延遲對良好的 UX 至關重要。LiveView 的 CSS 載入狀態,讓客戶端可以在等待伺服器回應時提供使用者回饋。在開發中,localhost 近乎零延遲,因此無法輕鬆地表示或測試延遲,因此 LiveView 在 JavaScript 客戶端包含延遲模擬器,以確保您的應用程式提供愉快的體驗。與上述 enableDebug()
函式類似,LiveSocket
執行個體包括 enableLatencySim(milliseconds)
和 disableLatencySim()
函式,這些函式會應用於當前瀏覽器作業階段。 enableLatencySim
函式會接收一個整數(單位為毫秒)來設定至伺服器的來回行程時間。舉例來說
// app.js
let liveSocket = new LiveSocket(...)
liveSocket.connect()
window.liveSocket = liveSocket
// in the browser's web console
>> liveSocket.enableLatencySim(1000)
[Log] latency simulator enabled for the duration of this browser session.
Call disableLatencySim() to disable
事件聆聽者
LiveView 會將多個事件發射到瀏覽器,並允許開發人員提交自己的事件。
Live 導覽事件
對於透過 <.link navigate={...}>
和 <.link patch={...}>
進行的即時頁面導覽,其伺服器端對應項 push_navigate
和 push_patch
,以及透過 phx-submit
提交的表單,JavaScript 事件 "phx:page-loading-start"
和 "phx:page-loading-stop"
會在視窗上傳遞。此外,任何 phx-
事件都可能透過加上帶有 phx-page-loading
的 DOM 元素註解來傳遞頁面載入事件。這對顯示主要頁面載入狀態很有用,例如
// app.js
import topbar from "topbar"
window.addEventListener("phx:page-loading-start", info => topbar.show())
window.addEventListener("phx:page-loading-stop", info => topbar.hide())
在回呼程式內, info.detail
會是一個包含 kind
鍵的物件,其值取決於觸發事件
"redirect"
- 事件由重新導向觸發"patch"
- 事件由修補程式觸發"initial"
- 事件由最初載入頁面觸發"element"
- 事件由phx-
繫結元素觸發,例如phx-click
"error"
- 事件由錯誤觸發,例如檢視崩毀或插座中斷連線
對於所有類型的頁面載入事件,除了 "element"
,所有其他事件都會在資訊中繼資料中收到另一個 to
鍵,指向與頁面載入相關的 href。
對於 "element"
頁面載入事件,資訊會包含一個 "target"
鍵,其中包含觸發頁面載入狀態的 DOM 元素。
較低層級的 phx:navigate
事件也會在 Phoenix 或使用者向前或向後導覽時,觸發瀏覽器的網址列出現程式設計化的變更。 info.detail
會包含以下資訊
"href"
- 網址列導覽到的位置。"patch"
- 布林旗標,指出這是一個修補程式導覽。"pop"
- 布林旗標,指出這是一個透過popstate
從使用者向前或向後瀏覽歷史記錄進行導覽。
處理伺服器推送事件
當伺服器使用 Phoenix.LiveView.push_event/3
時,事件名稱會在瀏覽器內以 phx:
的前置詞傳遞。例如,想像以下範本,您希望從伺服器中突顯現有的元素,以吸引使用者的注意力
<div id={"item-#{item.id}"} class="item">
<%= item.title %>
</div>
接下來,伺服器可以使用標準的 push_event
來發出亮顯色彩
def handle_info({:item_updated, item}, socket) do
{:noreply, push_event(socket, "highlight", %{id: "item-#{item.id}"})}
end
最後,window 活動偵聽器可以偵聽事件,並在元素符合條件時執行高亮顯示指令
let liveSocket = new LiveSocket(...)
window.addEventListener("phx:highlight", (e) => {
let el = document.getElementById(e.detail.id)
if(el) {
// logic for highlighting
}
})
如果需要,您也可以將此功能性與 Phoenix 的 JS 指令整合,在觸發亮顯時執行給定元素的 JS 指令。首先,更新元素以將 JS 指令嵌入資料屬性
<div id={"item-#{item.id}"} class="item" data-highlight={JS.transition("highlight")}>
<%= item.title %>
</div>
現在,在活動偵聽器中,使用 LiveSocket.execJS
來觸發新屬性中的所有 JS 指令
let liveSocket = new LiveSocket(...)
window.addEventListener("phx:highlight", (e) => {
document.querySelectorAll(`[data-highlight]`).forEach(el => {
if(el.id == e.detail.id){
liveSocket.execJS(el, el.getAttribute("data-highlight"))
}
})
})
透過 phx-hook
來進行使用者端掛鉤
在元素由伺服器新增、更新或移除時,要處理自訂使用者端的 JavaScript,可以使用掛鉤物件透過 phx-hook
來提供。 phx-hook
必須指向具有下列生命週期回呼的物件
mounted
- 元素已新增至 DOM,且其伺服器 LiveView 的安裝已完成beforeUpdate
- 元素將於 DOM 中更新。注意:在此處的任何呼叫都必須為同步,因為操作無法延遲或取消。updated
- 元素已由伺服器在 DOM 中更新destroyed
- 元素已從頁面移除,可能是由父更新移除,或由父代完全移除disconnected
- 元素的父 LiveView 已與伺服器斷線reconnected
- 元素的父 LiveView 已重新與伺服器連線
注意:在 LiveView 的內容外使用掛鉤時,mounted
是唯一會被呼叫的回呼,而且只有頁面上在 DOM 完成時存在的元素才會被追蹤。對於元素的動態追蹤,包括在新增、移除和更新時,應使用 LiveView。
以上生命週期回呼可以在範圍內存取以下屬性
el
- 屬性參考已繫結的 DOM 節點liveSocket
- 底層LiveSocket
實例的參考pushEvent(event, payload, (reply, ref) => ...)
- 從使用者端將事件推送到 LiveView 伺服器的函式pushEventTo(selectorOrTarget, event, payload, (reply, ref) => ...)
- 從使用者端將目標事件推送到 LiveViews 和 LiveComponents 的函式。它會將事件傳送至在selectorOrTarget
裡定義的 LiveComponent 或 LiveView 中,其值可以是查詢選擇器或實際的 DOM 元素。如果查詢選擇器傳回一個以上的元素,它會將事件傳送至全部的元素,即使所有元素都在同一個 LiveComponent 或 LiveView 中。pushEventTo
支援傳遞節點元素(例如this.el
)作為第一個目標參數,而不是選擇器(例如"#" + this.el.id
)。handleEvent(event, (payload) => ...)
- 處理從伺服器推動的事件的函式upload(name, files)
- 將類似檔案的物件清單注入到上傳器的方法。uploadTo(selectorOrTarget, name, files)
- 將類似檔案的物件清單注入到上傳器的方法。此掛鉤會將檔案傳送至上傳器,且name
由allow_upload/3
在伺服器端定義。傳送新上傳檔案會觸發輸入變更事件,並將傳送至selectorOrTarget
所定義的 LiveComponent 或 LiveView,其值可以是查詢選擇器或實際的 DOM 元素。如果查詢選擇器回傳多個即時檔案輸入,則會記錄錯誤。
例如,用於電話號碼格式化的受控輸入的標記可以用以下方式撰寫
<input type="text" name="user[phone_number]" id="user-phone-number" phx-hook="PhoneNumber" />
然後可以定義掛鉤回呼物件並傳遞至套接字
/**
* @type {Object.<string, import("phoenix_live_view").ViewHook>}
*/
let Hooks = {}
Hooks.PhoneNumber = {
mounted() {
this.el.addEventListener("input", e => {
let match = this.el.value.replace(/\D/g, "").match(/^(\d{3})(\d{3})(\d{4})$/)
if(match) {
this.el.value = `${match[1]}-${match[2]}-${match[3]}`
}
})
}
}
let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, ...})
...
注意:使用 phx-hook
時,必須一直設定唯一的 DOM ID。
對於整合需要廣泛存取完整 DOM 管理功能的客戶端端程式庫,LiveSocket
建構函接受具有 onBeforeElUpdated
回呼的 dom
選項。在 LiveView 的 DOM 修補作業發生之前,fromEl
和 toEl
DOM 節點會傳遞至函式。如此一來,外部程式庫便可重新初始化 DOM 元素或複製必要屬性,同時 LiveView 自行執行修補作業。更新作業無法取消或延後,而回傳值也會被忽略。
例如,下列選項可以用來保證在客戶端端設定的某部份屬性保持完整
...
let liveSocket = new LiveSocket("/live", Socket, {
params: {_csrf_token: csrfToken},
hooks: Hooks,
dom: {
onBeforeElUpdated(from, to) {
for (const attr of from.attributes) {
if (attr.name.startsWith("data-js-")) {
to.setAttribute(attr.name, attr.value);
}
}
}
}
}
在上述範例中,所有以 data-js-
開頭的屬性在 DOM 被 LiveView 修補時不會被替換。
客戶端與伺服器通訊
掛鉤可以使用 pushEvent
函式將事件推播至 LiveView,並透過 {:reply, map, socket}
回傳值從伺服器接收回應。回應酬載會傳遞至選用的 pushEvent
回應回呼。
可以透過在掛鉤元素上讀取資料屬性或是在伺服器上使用 Phoenix.LiveView.push_event/3
以及在客戶端上使用 handleEvent
,來與伺服器上的掛鉤進行通訊。
舉例來說,要實作無限捲動功能,可以使用資料屬性來傳送目前頁數
<div id="infinite-scroll" phx-hook="InfiniteScroll" data-page={@page}>
然後在客戶端
/**
* @type {import("phoenix_live_view").ViewHook}
*/
Hooks.InfiniteScroll = {
page() { return this.el.dataset.page },
mounted(){
this.pending = this.page()
window.addEventListener("scroll", e => {
if(this.pending == this.page() && scrollAt() > 90){
this.pending = this.page() + 1
this.pushEvent("load-more", {})
}
})
},
updated(){ this.pending = this.page() }
}
然而,如果你需要頻繁地將資料推播至客戶端,資料屬性方法並非一個好做法。要將帶外事件推播至客戶端(例如用來呈現圖表點),可以這麼做
<div id="chart" phx-hook="Chart">
{:noreply, push_event(socket, "points", %{points: new_points})}
然後在客戶端
/**
* @type {import("phoenix_live_view").ViewHook}
*/
Hooks.Chart = {
mounted(){
this.handleEvent("points", ({points}) => MyChartLib.addPoints(points))
}
}
透過 push_event
從伺服器推播的事件為全域事件,且會派送給處理該事件的客戶端上所有活動的勾子。如果您需要範疇化事件(例如從目前即時檢視上具有兄弟組件的即時組件推播),這必須透過命名空間來完成
def update(%{id: id, points: points} = assigns, socket) do
socket =
socket
|> assign(assigns)
|> push_event("points-#{id}", points)
{:ok, socket}
end
然後在客戶端
Hooks.Chart = {
mounted(){
this.handleEvent(`points-${this.el.id}`, (points) => MyChartLib.addPoints(points));
}
}
注意:萬一一個即時檢視推播事件並呈現內容,handleEvent
回呼會在頁面更新後呼叫。因此,如果即時檢視在推播事件的同時進行重新導向,回呼將不會在舊頁面的元素上呼叫。回呼會在重新導向頁面中新掛載的勾子元素上呼叫。