檢視原始碼 元件和 HEEx
需求:本指南希望您已完成 請求生命週期指南。
Phoenix 端點管線會接收請求,將其路由到控制器,並呼叫檢視模組來呈述範本。控制器中的檢視介面很簡單,由控制器呼叫連線變數的檢視函式,而函式的工作是回傳 HEEx 範本。我們呼叫任何接受 變數
參數並回傳 HEEx 範本的函式為函式元件。函式元件的定義是透過 Phoenix.Component
模組來達成。
函式元件是您在 Phoenix 中執行任何類型的標記語言範本呈述的基本構成模組。它們是標準 MVC 控制器為基礎的應用程式、LiveView 應用程式、配置範本以及您會在其他範本中使用的較小 UI 定義的共用抽象。
在本章節中,我們會回顧元件在先前章節中的使用方式並找出新的使用案例。
函式元件
在請求生命週期的章節最後,我們在 lib/hello_web/controllers/hello_html/show.html.heex
中建立了一個範本,讓我們來開啟它
<section>
<h2>Hello World, from <%= @messenger %>!</h2>
</section>
這個範本是嵌入於 HelloHTML
中,位置位於 lib/hello_web/controllers/hello_html.ex
defmodule HelloWeb.HelloHTML do
use HelloWeb, :html
embed_templates "hello_html/*"
end
這很簡單,只有兩行,use HelloWeb, :html
。這行呼叫 HelloWeb
中定義的 html/0
函式,用於設定我們的函式元件和範本的基本匯入和組態。
我們在模組中做出的匯入和別名也都會在我們的範本中生效。這是因為範本會有效地編譯成在其各自模組中的函式。例如,如果您在模組中定義函式,您就能直接從範本呼叫它。讓我們看看這如何實作。
想像我們想要對 show.html.heex
進行重構,以將 <h2>Hello World, from <%= @messenger %>!</h2>
的呈述移到它自己的函式中。我們可以將它移到 HelloHTML
中的函式元件中,我們就這樣做
defmodule HelloWeb.HelloHTML do
use HelloWeb, :html
embed_templates "hello_html/*"
attr :messenger, :string, required: true
def greet(assigns) do
~H"""
<h2>Hello World, from <%= @messenger %>!</h2>
"""
end
end
我們透過Phoenix.Component
提供的attr/3
巨集宣告我們接受的屬性,然後定義greet/1
函數,回傳HEEx範本。
接下來我們需要更新show.html.heex
<section>
<.greet messenger={@messenger} />
</section>
當我們重新載入https://127.0.0.1:4000/hello/Frank
時,我們應該會看到和先前相同的內容。
由於範本是嵌入在HelloHTML
模組裡,因此我們能夠直接呼叫檢視函數,如<.greet messenger="..." />
。
如果組件定義在其他地方,我們也可以輸入<HelloWeb.HelloHTML.greet messenger="..." />
。
透過宣告屬性為必要的,如果我們呼叫<.greet />
組件而沒有傳遞屬性時,Phoenix會在編譯時發出警告。如果某個屬性是可選的,你可以指定:default
選項加上一個值
attr :messenger, :string, default: nil
雖然這是一個簡單的範例,它展示了Phoenix中函數組件扮演的不同角色
函數組件可以定義為接收
assigns
作為引數的函數,並呼叫~H
標記,正如我們在greet/1
中所做的一樣函數組件可以從範本檔案嵌入,這就是我們載入
show.html.heex
到HelloWeb.HelloHTML
的方式函數組件可以宣告預期的屬性,這些屬性會在編譯時驗證
函數組件可以從控制器直接渲染
函數組件可以從其他函數組件直接渲染,正如我們從
show.html.heex
呼叫<.greet messenger={@messenger} />
一樣
而還有更多。在我們深入探討之前,讓我們先徹底了解HEEx範本語言背後的表示能力。
HEEx
函數組件和範本檔案是由HEEx範本語言驅動的,其中HEEx代表「HTML+EEx」。EEx是一個Elixir函式庫,它使用<%= expression %>
來執行Elixir表達式,並將其結果插入到範本中。這通常用於顯示我們透過@
捷徑所設定的assigns。在你的控制器中,如果你呼叫
render(conn, :show, username: "joe")
接下來你就可以在範本中存取上述的使用者名稱,方式為<%= @username %>
。除了顯示assigns和函數外,我們幾乎可以使用任何Elixir表達式。例如,為了要有條件判斷
<%= if some_condition? do %>
<p>Some condition is true for user: <%= @username %></p>
<% else %>
<p>Some condition is false for user: <%= @username %></p>
<% end %>
甚至是迴圈
<table>
<tr>
<th>Number</th>
<th>Power</th>
</tr>
<%= for number <- 1..10 do %>
<tr>
<td><%= number %></td>
<td><%= number * number %></td>
</tr>
<% end %>
</table>
你注意到上面 <%= %>
與 <% %>
的用法差異嗎?所有輸出內容至範本的表達式必須使用等號 (=
)。如果未包含等號,程式碼仍會執行,但不會產生任何輸出結果。
HEEx 也具備我們接下來將學習的實用 HTML 延伸功能。
HTML 延伸功能
除了允許透過 <%= %>
內插 Elixir 表達式外,.heex
範本還具備具 HTML 感知能力的延伸功能。例如,讓我們看看在其中內插含有 "<" 或 ">" 的值會發生什麼情況,這會導致 HTML 注入
<%= "<b>Bold?</b>" %>
當你渲染範本時,你會看到字面上的 <b>
出現在頁面上。這表示使用者無法在頁面上注入 HTML 內容。如果你想要允許他們這麼做,你可以呼叫 raw
,但必須非常謹慎
<%= raw "<b>Bold?</b>" %>
HEEx 範本的另一個超能力是驗證 HTML 和屬性的精簡內插語法。你可以撰寫
<div title="My div" class={@class}>
<p>Hello <%= @username %></p>
</div>
請注意,你可以簡單地使用 key={value}
。HEEx 會自動處理特殊值(例如 false
),以移除屬性或類別清單。
若要內插一組動態屬性至關鍵字清單或對映,請執行
<div title="My div" {@many_attributes}>
<p>Hello <%= @username %></p>
</div>
另外,請嘗試移除關閉 </div>
,或將其重新命名為 </div-typo>
。HEEx 範本將會告知你錯誤。
HEEx 也支援透過特殊屬性 :if
與 :for
的精簡語法表示 if
與 for
表達式。例如,使用此方式替代以下範例
<%= if @some_condition do %>
<div>...</div>
<% end %>
你可以撰寫
<div :if={@some_condition}>...</div>
同樣地,對於推論式,可以撰寫為
<ul>
<li :for={item <- @items}><%= item.name %></li>
</ul>
配置
配置僅是函式組件。它們是在模組中定義的,就像其他所有函式組件範本一樣。在新生成的應用程式中,這是 lib/hello_web/components/layouts.ex
。你還會找到 layouts
資料夾,其中包含兩個由 Phoenix 生成的內建配置。預設的根配置稱為 root.html.heex
,這是預設會將所有範本渲染至其中的配置。第二個是應用程式配置,稱為 app.html.heex
,會在根配置中渲染,並包含我們的內容。
你可能會想知道,從渲染檢視產生的字串是如何進入配置中。這是一個好問題!如果我們檢視 lib/hello_web/components/layouts/root.html.heex
,在 <body>
的結尾附近會看到下面這段。
<%= @inner_content %>
換句話說,渲染你的頁面後,結果會放入 @inner_content
中。
Phoenix 提供了各式各樣的便利方式來控制要呈現哪種版面。例如,Phoenix.Controller
模組提供了 put_root_layout/2
函式供我們切換根版面。此函式的第一個參數為 conn
,第二個參數則為格式及其版面的關鍵字清單。你可以將它設為 false
來停用版面。
你可以編輯 lib/hello_web/controllers/hello_controller.ex
中 HelloController
的 index
動作,如下所示。
def index(conn, _params) do
conn
|> put_root_layout(html: false)
|> render(:index)
end
重新載入 https://127.0.0.1:4000/hello 後,我們應該會看到一個截然不同的頁面,這個頁面完全沒有標題或 CSS 造型。
如要自訂應用程式版面,我們會呼叫一個名為 put_layout/2
類似函式。實際上,我們來建立另一個版面並將索引範本呈現在其中。舉例來說,我們可以為應用程式的管理員區段製作一個不同的版面,其中沒有標誌影像。為執行這項操作,請將現有的 app.html.heex
複製到同一個目錄 lib/hello_web/components/layouts
中的新檔案 admin.html.heex
。接著移除新檔案中 <header>...</header>
標籤內的全部內容(或變更為你想要的內容)。
現在,在 lib/hello_web/controllers/hello_controller.ex
控制器中的 index
動作中加入下列內容
def index(conn, _params) do
conn
|> put_layout(html: :admin)
|> render(:index)
end
載入頁面時,我們應該會呈現沒有標頭(或你編寫的自訂標頭)的管理員版面。
在這個階段,你可能會好奇,Phoenix 為什麼要有兩個版面?
首先,這讓我們能夠靈活運用。實際上,我們不太可能有多個根版面,因為它們通常只包含 HTML 標頭。這讓我們可以將心力放在不同的應用程式版面,以及它們之間變更的部分。其次,Phoenix 附帶了一個名為 LiveView 的功能,讓我們能夠藉由伺服器呈現的 HTML 建立豐富且即時的使用者體驗。LiveView 能夠動態變更頁面內容,但它只會變更應用程式版面,絕不會變更根版面。查看LiveView 文件以進一步了解。
核心組件
在一個新的 Phoenix 應用程式中,你還可以在 components
資料夾中找到一個 core_components.ex
模組。這個模組是定義函式組件的範例,可重複用於整個應用程式中。藉由這個做法,當應用程式持續演進時,我們的組件也能持續保持一致的樣貌。
如果您查看設在 lib/hello_web.ex
中的 HelloWeb
的 def html
,您會看到 CoreComponents
已透過 use HelloWeb, :html
自動匯入至所有 HTML 檢視。這也是 CoreComponents
自身在最上方執行 use Phoenix.Component
而不是 use HelloWeb, :html
的原因:後者的作法會造成死結,因為我們會試圖將 CoreComponents
匯入自身。
由於產生器代碼假設那些元件可用來快速建立架構應用程式,所以 CoreComponents 在 Phoenix 產生器代碼中也會扮演重要的角色。如果您想要進一步了解所有這些區塊,您可以
探索已產生的
CoreComponents
模組,從實際範例中學習更多詳細內容閱讀
Phoenix.Component
的官方文件閱讀 HEEx 和 ~H 標章 的官方文件