Phoenix 1.7.0 發佈:內建 Tailwind、驗證路由、LiveView 串流,接下來是什麼
由 Chris McCord 在 2023 年 2 月 24 日發佈
Phoenix 1.7 的最後版本出爐!Phoenix 1.7 包含許多期待已久的全新功能,例如驗證路由、Tailwind 支援、LiveView 驗證產生器、統一 HEEx 範本、最佳化集合的 LiveView 串流,以及更多。這是一個向下相容的版本,有一些棄用函式。大多數人只要變更幾個相依性就能更新。
注意:若要產生新的 1.7 專案,您需要從 hex 安裝 phx.new
產生器
mix archive.install hex phx_new
驗證路由
驗證路由使用基於 sigil (~p
) 的編譯時期驗證方法,取代路由輔助工具。
注意:驗證路由使用新的 Elixir 1.14 編譯器功能。Phoenix 仍支援舊版 Elixir,但您需要更新才能享用新的編譯時期驗證功能。
實際上,這表示您以前使用自動產生的函式,例如
# router
get "/oauth/callbacks/:id", OAuthCallbackController, :new
# usage
MyRouter.Helpers.o_auth_callback_path(conn, :new, "github")
# => "/oauth/callbacks/github"
MyRouter.Helpers.o_auth_callback_url(conn, :new, "github")
# => "https://127.0.0.1:4000/oauth/callbacks/github"
現在可以執行
# router
get "/oauth/callbacks/:id", OAuthCallbackController, :new
# usage
~p"/oauth/callbacks/github"
# => "/oauth/callbacks/github"
url(~p"/oauth/callbacks/github")
# => "https://127.0.0.1:4000/oauth/callbacks/github"
這有許多優點。不用再猜測要使用哪個函式,例如 Helpers.oauth_callback_path
或 o_auth_callback_path
等等。您也不再需要處處包含 %Plug.Conn{}
、%Phoenix.Socket{}
或端點模組,因為在 99% 的情況下您知道應該使用哪個端點組態。
您現在可以一一對應路由中編寫的路由,以及使用 ~p
呼叫路由的方法。您只要將它寫成在 app 中每處都硬編碼字串的樣子,不同之處在於沒有硬編碼字串會帶來的維護問題。我們可以使用 ~p
,根據路由中的路由進行編譯時期驗證,輕鬆兼得方便性和維護性。
例如,想像我們拼錯路由
<.link href={~p"/userz/profile"}>Profile</.link>
編譯器會在編譯時期針對您的路由傳遞所有 ~p
,並在找不到相符路由時通知您
warning: no route path for AppWeb.Router matches "/postz/#{post}"
lib/app_web/live/post_live.ex:100: AppWeb.PostLive.render/1
動態「命名參數」也會像一般字串那樣內插,而不是使用任意函式參數
~p"/posts/#{post.id}"
此外,內插的 ~p
值會透過 Phoenix.Param
協定編碼。例如,應用程式中的 %Post{}
結構可以衍生 Phoenix.Param
協定,產生基於 slug 的路徑,而不是基於 ID 的路徑。這樣您就可以在應用程式中使用 ~p"/posts/#{post}"
,而不是 ~p"/posts/#{post.slug}"
。
驗證路由也支援查詢字串,可以是傳統的查詢字串格式
~p"/posts?page=#{page}"
或關鍵字清單或值對應
params = %{page: 1, direction: "asc"}
~p"/posts?#{params}"
與路徑區段類似,查詢字串參數會適當地進行 URL 編碼,並可能直接插補到 ~p
字串中。
一旦你試用新功能,你將無法再回到路由輔助程式了。新的 phx.gen.html|live|json|auth
產生器使用已驗證的路由。
基於元件的 Tailwind 產生器
Phoenix 1.7 預設搭載 TailwindCSS,且不依賴系統中的 nodejs。在我 20 年的網路開發經驗中,TailwindCSS 是我發現用於為介面造型的最佳方式。相較於我曾使用的任何 CSS 系統或架構,其實用工具優先的作法更具可維護性和生產力。其並置的作法也完美地符合功能元件和 LiveView 的架構。
Tailwind 團隊也很慷慨地為新的專案設計了新的專案登陸頁面、CRUD 頁面和驗證系統頁面,為你的應用程式建置提供一流且完善的起點。
新的 phx.new
專案將包含一個 CoreComponents
模組,其中包含一組 UI 核心元件,例如表格、模態框、表單和資料清單。Phoenix 產生器套件(phx.gen.html|live|json|auth
)使用核心元件。這擁有許多絕佳的優點。
首先,你可以自訂核心 UI 元件,以滿足任何你需要、設計和品味。如果你想使用 Bulma 或 Bootstrap,而不是 Tailwind ── 沒問題!只要使用你的架構/特定 UI 的實作取代 core_components.ex
中的功能定義,產生器便會持續提供一個絕佳的起點,無論你是一位初學者,或是有豐富經驗的專家建置客製化產品功能。
實際上,產生器會提供使用你的核心元件的範本,如下所示
<.header>
New Post
<:subtitle>Use this form to manage post records in your database.</:subtitle>
</.header>
<.simple_form for={@form} action={~p"/posts"}>
<input field={@form[:title]} type="text" label="Title" />
<input field={@form[:views]} type="number" label="Views" />
<:actions>
<.button>Save Post</.button>
</:actions>
</.simple_form>
<.back navigate={~p"/posts"}>Back to posts></.back>
我們很喜歡 Tailwind 團隊為新應用程式設計的內容,但也迫不及待想看到社群釋出他們自己的 core_components.ex
安裝程式替代方案,供各種選擇架構使用。
跨控制器和 LiveView 的統一功能元件
HEEx 提供的功能元件,具有宣示指派和位置,為在 Phoenix 專案中撰寫 HTML 的方式帶來重大的步驟變革。功能元件提供 UI 建構區塊,讓功能封裝起來,並比之前的 Phoenix.View
範本方法有更好的擴充性。你獲得了撰寫動態標記的更自然方式、可重複使用的 UI(呼叫者可以擴充),以及編譯時期功能,讓撰寫基於 HTML 的應用程式成為真正的首要體驗。
Phoenix.View
功能元件帶來撰寫 Phoenix HTML 應用程式的新方式,並設定了新的慣例。此外,使用者會對於如何將以控制器為基礎的 Phoenix.View
功能與應用程式中的 Phoenix.LiveView
功能結合而苦惱。使用者發現自己會在以控制器為基礎的範本中撰寫 render("table", user: user)
,而他們的 LiveView 則使用新 <.table rows={@users}>
功能。在應用程式中並未有絕佳的方式來分享這些做法。
基於這些原因,Phoenix 團隊統一了 HTML 顯示方式,無論是來自控制器要求或 LiveView。這個改變也讓我們能夠重新檢視慣例,並配合 LiveView 將範本和應用程式程式碼放在一起的做法。
新的應用程式(以及 phx 產生器)移除 Phoenix.View
作為相依性,並偏好新的 Phoenix.Template
相依性,這個相依性使用函式元件作為此架構中所有顯示的基礎。
控制器的樣式維持不變
defmodule AppWeb.UserController do
use MyAppWeb, :controller
def index(conn, _params) do
users = ...
render(conn, :index, users: users)
end
end
但是,控制器不再呼叫 AppWeb.UserView.render("index.html", assigns)
,現在我們會先在檢視模組中尋找 index/1
函式元件,如果找到,則呼叫該元件執行顯示。此外,我們也重新命名轉換的檢視模組,以尋找 AppWeb.UserHTML
或 AppWeb.UserJSON
,等等,以採用針對特定格式檢視範本的做法。這一切都以向後相容的方式執行,並根據 use Phoenix.Controller
的選項進行選擇。
所有 HTML 顯示都是根據函式元件執行,這些元件可以直接在模組中撰寫,或以 Phoenix.Component
提供的新 embed_templates
巨集從外部檔案嵌入。新的應用程式中的 PageHTML
模組看起來如下所示
defmodule AppWeb.PageHTML do
use AppWeb, :html
embed_templates "page_html/*"
end
新的目錄結構看起來會像這樣
lib/app_wb
├── controllers
│ ├── page_controller.ex
│ ├── page_html.ex
│ ├── error_html.ex
│ ├── error_json.ex
│ └── page_html
│ └── home.html.heex
├── live
│ ├── home_live.ex
├── components
│ ├── core_components.ex
│ ├── layouts.ex
│ └── layouts
│ ├── app.html.heex
│ └── root.html.heex
├── endpoint.ex
└── router.ex
現在,你的控制器顯示或 LiveView 顯示都使用相同的函式元件和配置。執行 phx.gen.html
、phx.gen.live
或 phx.gen.auth
時,新產生的範本都會使用你的 components/core_components.ex
定義。
此外,我們將檢視模組並列放置在其控制器檔案旁邊。這帶來了 LiveView 並置帶來的相同好處-高度關聯的檔案放在一起。必須一起變更的檔案現在放在一起,無論是撰寫 LiveView 或控制器功能。
這些變更都是為了改善撰寫 HTML 基礎應用程式的優化方式,但它們也簡化了其他格式的呈現,例如 JSON。例如,基於 JSON 的檢視模組遵循相同的慣例 – 在嘗試 render/2
之前,Phoenix 會在呈現索引範本時先尋找 index/1
函數。這讓我們可以簡化一般的 JSON 呈現,並消除 Phoenix.View.render_one|render_many
等概念。
例如,這是由 phx.gen.json
產生的 JSON 檢視
defmodule AppWeb.PostJSON do
alias AppWeb.Blog.Post
@doc """
Renders a list of posts.
"""
def index(%{posts: posts}) do
%{data: for(post <- posts, do: data(post))}
end
@doc """
Renders a single post.
"""
def show(%{post: post}) do
%{data: data(post)}
end
defp data(%Post{} = post) do
%{
id: post.id,
title: post.title
}
end
end
請注意,它如何全部只是常規 Elixir 函數 - 它應該如此!
這些功能透過一種採用新的且進步的方式來撰寫 UI 的方式,為應用程式提供統一的呈現模型,但它們與先前的作法有所不同。大多數大型且已建立的應用程式最適合持續依賴 Phoenix.View
。
LiveView 串流
LiveView 現在包含用於管理 UI 中的大型集合的串流介面,而無需將集合儲存在伺服器的記憶體中。透過呼叫幾個函數,您可以在 UI 中插入新的項目,動態追加或前置,或重新排列項目,而無需在伺服器上重新載入這些項目。
Phoenix 1.7 中的 phx.gen.live
live CRUD 產生器使用串流來管理您的項目清單。這允許資料輸入、更新和刪除,而無需在初始載入後重新擷取項目清單。讓我們看看如何進行。
當您執行 mix phx.gen.live Blog Post posts title views:integer
時,將產生下列 PostLive.Index
模組
defmodule DemoWeb.PostLive.Index do
use DemoWeb, :live_view
alias Demo.Blog
alias Demo.Blog.Post
@impl true
def mount(_params, _session, socket) do
{:ok, stream(socket, :posts, Blog.list_posts())}
end
...
end
請注意,我們有一個新的 stream/3
介面,而不是常規的 assign(socket, :posts, Blog.list_posts())
。這會設定具有初始文章集合的串流。然後,在產生的 index.html.heex
範本中,我們會使用串流來呈現文章表格
<.table
id="posts"
rows={@streams.posts}
row_click={fn {_id, post} -> JS.navigate(~p"/posts/#{post}") end}
>
<:col :let={{_id, post}} label="Title"><%= post.title %></:col>
<:col :let={{_id, post}} label="Views"><%= post.views %></:col>
<:action :let={{_id, post}}>
<div class="sr-only">
<.link navigate={~p"/posts/#{post}"}>Show</.link>
</div>
<.link patch={~p"/posts/#{post}/edit"}>Edit</.link>
</:action>
<:action :let={{id, post}}>
<.link
phx-click={JS.push("delete", value: %{id: post.id}) |> hide("##{id}")}
data-confirm="Are you sure?"
>
Delete
</.link>
</:action>
</.table>
這看起來與舊範本非常相似,但我們將 @stream.posts
傳遞給我們的表格,而不是存取赤裸的 @posts
指定。使用串流時,我們也會收到串流的 DOM ID,連同項目一起。
回到伺服器,我們可以看到在表格中插入新項目是多麼簡單。當我們產生的 FormComponent
透過表單更新或儲存文章時,我們會將訊息包傳送給關於新的或已更新文章的父級 PostLive.Index
LiveView
PostLive.FormComponent
:
defmodule DemoWeb.PostLive.FormComponent do
...
defp save_post(socket, :new, post_params) do
case Blog.create_post(post_params) do
{:ok, post} ->
notify_parent({:saved, post})
{:noreply,
socket
|> put_flash(:info, "Post created successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign_form(socket, changeset)}
end
end
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
end
然後我們在 PostLive.Index
handle_info
子句中選取訊息
@impl true
def handle_info({DemoWeb.PostLive.FormComponent, {:saved, post}}, socket) do
{:noreply, stream_insert(socket, :posts, post)}
end
所以,此表單告訴我們它儲存了一篇文章,而我們只要在串流中將文章串流插入
即可。就是這樣!如果使用者介面中已經存在文章,就會在原處進行更新。否則,它會預設附加到容器中。您還可以透過stream_insert(socket, :posts, post, at: 0)
進行前置新增,或將任何索引傳遞給:at
以進行任意項目插入或重新排序。
串流是我們前往 LiveView 1.0 的最後一塊磚石,我很高興我們能順利抵達。
新的表單欄位資料結構
我們都很熟悉 Phoenix.HTML 表單的基本碼元 <.form for={@changeset}>
,其中表單會接收實作Phoenix.HTML.FormData
協定的資料結構並傳回 %Phoenix.HTML.Form{}
。我們的做法遇到一個問題是,表單資料結構無法追蹤個別表單欄位的變更。這讓 LiveView 中的最佳化變為不可能,因為每次個別變更都必須重新渲染並重新傳送表單。透過引入 Phoenix.HTML.FormData.to_form
和 Phoenix.Component.to_form
,我們現在針對個別欄位變更建立了 %Phoenix.HTML.FormField{}
資料結構。
新的 phx.gen.live
產生器和您的 core_components.ex
利用了這些新功能。
Phoenix 和 LiveView 的下一步是什麼
Phoenix 產生器利用了 LiveView 的最新功能,而且未來會持續擴充。隨著串流集合成為預設,我們可以朝向 Live CRUD 產生器(phx.gen.live
)的現成進階功能邁進。例如,我們計畫為資源推出現成的同步使用者介面。已產生的 Phoenix 表單功能會持續演進,並加入新的to_form
介面。
對於 LiveView,to_form
讓我們能夠傳送最佳化表單的基礎。現在,個別變更只會產生一個最佳化差異。
在這個最佳化工作後,LiveView 1.0 的主要功能剩餘擴充表單 API,能更好支援動態表單輸入、精靈樣式表單和委派表單輸入給子級 Live 組件。
備用 Web 伺服器支援
多虧 Mat Trudel 的努力,我們現在有基礎能在 Plug 和 Phoenix 中提供一流的 Web 伺服器支援,讓使用者可以在 Phenix 中換用其他 Web 伺服器,例如 Bandit,同時享受 WebSockets、Channels 和 LiveView 等所有功能。如果您有興趣使用純 Elixir HTTP 伺服器或在您自己的 Phoenix 專案中試試 Bandit,請密切關注 Bandit 專案!
下一步
和往常一樣,分步升級指南 可以將您現有的 1.6.x 應用程式升級到 1.7。
您可以在此找到完整的變更紀錄。
如果您遇到問題,請在 Elixir slack 或論壇中找到我們。
編寫程式愉快!
-Chris