檢視原始碼 指派和 HEEx 範本
LiveView 中的資料全部儲存在 Socket 中,是一個名為 Phoenix.LiveView.Socket
的伺服器端結構。您的資料會儲存在上述結構的 assigns
鍵中。伺服器資料絕不會透過範本呈現的範圍外分享給用戶端。
Phoenix 範本語言稱為 HEEx (HTML+EEx)。EEx 是嵌入式 Elixir,一種 Elixir 字串範本引擎。這些範本可能是副檔名為 .heex
的檔案,或是透過 ~H
sigil 直接在原始檔中建立。若要進一步了解 HEEx 語法,請查看 ~H
sigil 的文件。
Phoenix.Component.assign/2
和 Phoenix.Component.assign/3
函式有助於儲存那些值。這些值可以在 LiveView 中作為 socket.assigns.name
存取,但在 HEEx 範本中則作為 @name
存取。
在本節中,我們將了解 LiveView 如何透過了解指派和範本的交互作用,來將通過網路傳輸的負載減到最少。
變更追蹤
當您第一次呈現 .heex
範本時,將會將範本的所有靜態和動態部分傳送至用戶端。想像以下範本:
<h1><%= expand_title(@title) %></h1>
它包含兩個靜態部分,<h1>
和 </h1>
,以及一個由 expand_title(@title)
組成的動態部分。此範本的後續呈現並不會重新傳送靜態部分,而且僅在變更時才會重新傳送動態部分。
變更追蹤是透過指派來進行的。如果 @title
指派變更,LiveView 會執行範本的動態部分 expand_title(@title)
,並傳送新內容。如果 @title
相同,則不會執行任何動作且也不傳送任何內容。
變更追蹤在存取映射/結構域時也可以運作。拿這個範本來說:
<div id={"user_#{@user.id}"}>
<%= @user.name %>
</div>
如果 @user.name
變更,但 @user.id
沒有變更,LiveView 將會重新呈現 @user.name
,且完全不會執行或重新傳送 @user.id
。
變更追蹤在呈現其他範本時也可以運作,前提是它們也是 .heex
範本
<%= render "child_template.html", assigns %>
或是在使用函式組件時都可以運作
<.show_name name={@user.name} />
追蹤指定功能也表示你必須避免在範本中執行直接操作。例如,如果你在範本中執行資料庫查詢
<%= for user <- Repo.all(User) do %>
<%= user.name %>
<% end %>
那麼 Phoenix 將永遠不會重新呈現上述區段,即使資料庫中的使用者數目有變。相反地,你需要在 LiveView 呈現範本之前將使用者儲存在指派中
assign(socket, :users, Repo.all(User))
一般而言,資料載入永遠不應該發生在範本內部,無論你是否使用 LiveView。差別在於 LiveView 會強制執行此項最佳實務。
潛在問題
在 LiveView 內部使用 ~H
標誌或 .heex
範本時,有些常見潛在問題需要注意。
變數
由於變數的範圍,LiveView 必須在範本中使用變數時停用變更追蹤,但由 Elixir 區塊結構引入的變數例外,例如 case
、for
、if
等。因此,你必須避免在 HEEx 範本中使用類似這樣的程式碼
<% some_var = @x + @y %>
<%= some_var %>
改用函式
<%= sum(@x, @y) %>
同樣地,不要在 LiveView 或 LiveComponent 的 render
函式的頂端定義變數。由於 LiveView 無法追蹤 sum
或 title
,如果任一值變更,LiveView 兩者都必須重新呈現。
def render(assigns) do
sum = assigns.x + assigns.y
title = assigns.title
~H"""
<h1><%= title %></h1>
<%= sum %>
"""
end
改用 assign/2
、assign/3
、assign_new/3
和 update/3
函式來運算它。任何由此方式定義或更新的指派都會標示為已變更,而其他指派(例如 @title
)仍會由 LiveView 追蹤。
assign(assigns, sum: assigns.x + assigns.y)
這些函式也可以使用在函式元件中
attr :x, :integer, required: true
attr :y, :integer, required: true
attr :title, :string, required: true
def sum_component(assigns) do
assigns = assign(assigns, sum: assigns.x + assigns.y)
~H"""
<h1><%= @title %></h1>
<%= @sum %>
"""
end
一般而言,避免在 HEEx
範本中存取變數,因為存取變數的程式碼會在每次呈現時執行。例外是 Elixir 區塊結構中引入的變數。例如,存取由下方萃取運算所定義的 post
變數會有效運作
<%= for post <- @posts do %>
...
<% end %>
assigns
變數
在討論變數時,assigns
特殊變數也有討論的價值。每次你使用 ~H
標誌,你都必須定義一個 assigns
變數,每個 .heex
範本中也都找得到此變數。然而,我們必須避免在範本中直接存取此變數,改用 @
存取特定金鑰。這也適用於函式元件。讓我們來看看一些範例。
有時您可能會希望從一個函數元件傳遞所有指派到另一個函數元件。例如,想像您有一個複雜的 card
元件,它有標題、內容和頁尾區段。您可以在內部把元件重構為三個較小的元件
def card(assigns) do
~H"""
<div class="card">
<.card_header {assigns} />
<.card_body {assigns} />
<.card_footer {assigns} />
</div>
"""
end
defp card_header(assigns) do
...
end
defp card_body(assigns) do
...
end
defp card_footer(assigns) do
...
end
由於函數元件處理屬性的方式,上述程式碼不會執行變更追蹤,而且會在每次變更時重新渲染所有三個元件。
通常,您應該避免傳遞所有指派,而應明確指出子元件需要哪些指派
def card(assigns) do
~H"""
<div class="card">
<.card_header title={@title} class={@title_class} />
<.card_body>
<%= render_slot(@inner_block) %>
</.card_body>
<.card_footer on_close={@on_close} />
</div>
"""
end
如果您真的需要傳遞所有指派,您應該改用常規的函數呼叫語法。這是模板中唯一可以存取 assigns
的情況
def card(assigns) do
~H"""
<div class="card">
<%= card_header(assigns) %>
<%= card_body(assigns) %>
<%= card_footer(assigns) %>
</div>
"""
end
這樣確保了母元件的變更追蹤資訊會傳遞到每個子元件,只重新渲染必要的部份。然而,一般來說,最好避免傳遞 assigns
,而是讓 LiveView 找出追蹤變更的最佳方式。
摘要
總結來說
避免在 HEEx 範本中定義局部變數,特別是在 Elixir 的結構中
避免在 HEEx 範本中傳遞或存取
assigns
變數