領先一步
VMware 提供培訓和認證,助您快速提升。
瞭解更多如今,前端開發主要由大型 JavaScript 客戶端框架主導。這有很多充分的理由,但對於許多用例來說,效率非常低,而且框架工程變得極其複雜。在本文中,我想探討一種不同的方法,一種更高效、更靈活的方法,它由更小的構建塊構建而成,非常適合 Spring 等伺服器端應用框架(或各種伺服器端語言中的類似工具)。其理念是擁抱超媒體的概念,想象下一代瀏覽器將如何利用它,並使用少量 JavaScript 將當今的瀏覽器增強到該水平。現代瀏覽器會忽略 HTML 中的自定義元素和屬性,但它們允許內容作者使用 JavaScript 定義其行為。目前已有幾個庫可以幫助實現這一點,我們將研究 HTMX、Unpoly 和 Hotwired Turbo。我們還將探討如何將這些庫與 Spring Boot 結合使用,以及如何將它們與像 Thymeleaf 這樣的傳統伺服器端框架結合使用。
您可以在 GitHub 上找到原始碼 (dsyer/webmvc-thymeleaf)。"main" 分支是起始點,並且有對應我們要探索的每個庫的分支。
作為起始點,我們將使用一個簡單但不簡單的 Spring Boot 應用,它使用 thymeleaf。它最初是為 Thymeleaf 和 Spring Webmvc 的效能測試而建立的,所以我們希望它有一些“真實”的應用特性,但不需要資料庫或任何其他依賴。有兩個選項卡,一個是靜態的(見 SampleController
)
@GetMapping(path = "/")
String user(Map<String, Object> model) {
model.put("message", "Welcome");
model.put("time", new Date());
return "index";
}
另一個帶有一個使用者可以提交的表單,用於建立問候語
@PostMapping(path = "/greet")
String name(Map<String, Object> model, @RequestParam String name) {
greet(model);
model.put("greeting", "Hello " + name);
model.put("name", name);
return "greet";
}
這兩個選項卡都在伺服器上渲染為獨立的頁面,但它們使用一個共享的 layout.html
模板來顯示頁首和頁尾。有一個 messages.properties
檔案包含一些可國際化的內容,儘管目前只包含了預設的英文版本。
應用中唯一的 JavaScript 和 CSS 在 layout.html
模板中,用於在窄螢幕上切換選項卡頭部。這是一個簡單的漸進增強示例,也是我們探索超媒體和瀏覽器增強的良好起點。
要在 IDE 中執行應用,請使用 WebmvcApplicationTests
中的 main()
方法;或在命令列中使用 ./mvnw spring-boot:test-run
。
我們可以首先將 HTMX 新增到應用程式中。HTMX 是一個小型 JavaScript 庫,允許您在 HTML 中使用自定義屬性來定義頁面中元素的行為。它有點像現代版的 onclick
屬性,但功能更強大、更靈活。它也更高效,因為它使用瀏覽器內建的 HTTP 棧來發出請求,並且可以使用瀏覽器內建的快取和歷史管理功能。它非常適合 Spring Boot 等伺服器端框架,因為它允許您使用伺服器來生成頁面內容和行為,並且允許您使用瀏覽器內建功能來管理導航和歷史記錄。
最簡單的方法是從 CDN 獲取它並將其新增到 layout.html
模板中
<script src='https://unpkg.com/htmx.org/dist/htmx.min.js'></script>
除了這種方式,在示例程式碼的 "htmx" 分支中,我們使用了 Webjar 將庫載入到 classpath 中,這同樣可行。Spring 可以做一些額外的事情來幫助瀏覽器快取庫,並且還可以幫助進行版本管理。
我們可以輕鬆新增的一個功能是使用 HTMX 在不重新載入整個頁面的情況下提交表單。我們可以透過向 form 元素新增 hx-post
屬性來實現
<form th:action="@{/greet}" method="post" hx-post="/greet">
<input type="text" name="name" th:value="${name}"/>
<button type="submit" class="btn btn-primary">Greet</button>
</form>
這將導致 HTMX 攔截表單的提交操作,並使用 AJAX 請求將資料傳送到伺服器。伺服器將處理請求並返回結果,HTMX 將用結果替換表單的內容。
在這種情況下,這並不是我們想要的,因為表單控制頁面上另一個(兄弟)元素中的一些內容。我們可以透過向 form 元素新增 hx-target
屬性來解決這個問題
<form th:action="@{/greet}" th:hx-post="@{/greet}" method="post" hx-target="#content">
其中 "content" 元素已透過 ID 標識。回到該元素,我們需要其 ID 以及 hx-swap-oob
屬性,以告知 HTMX 傳入的內容應該替換現有內容(與原始提交操作“帶外”)。
<div id="content" class="col-md-12" hx-swap-oob="true">
<span th:text="${greeting}">Hello, World</span><br/>
<span th:text="${time}">21:00</span>
</div>
透過在 greet.html
模板中進行這兩個小改動,我們就可以在不重新載入整個頁面的情況下向伺服器提交表單並更新頁面。如果您現在提交表單,並檢視瀏覽器開發者工具中的網路活動,您會看到伺服器正在重新渲染整個頁面,但 HTMX 正在提取“content”元素併為我們切換其內容。影像和其他靜態內容不會重新載入,瀏覽器的歷史記錄也會更新以反映頁面的新狀態。
您可能還會注意到 HTMX 會在向伺服器發出的請求中新增 hx-request
頭部。這是 HTMX 的一個特性,允許您在伺服器端程式碼中匹配請求,接下來我們將使用它。
伺服器在表單提交時仍然渲染整個頁面,但我們可以透過使用片段模板來提高效率。我們可以透過向 greet.html
模板新增 th:fragment
屬性來實現
<div id="content" th:fragment="content" class="col-md-12" hx-swap-oob="true">
<span th:text="${greeting}">Hello, World</span><br/>
<span th:text="${time}">21:00</span>
</div>
然後我們可以在 SampleController
中的一個新的對映方法中使用該片段,該方法僅在請求來自 HTMX 時觸發(透過匹配 hx-request
頭部)
@PostMapping(path = "/greet", headers = "hx-request=true")
String nameHtmx(Map<String, Object> model, @RequestParam String name) {
greet(model);
return "greet :: content";
}
(“::”語法是 Thymeleaf 的一個特性,允許您渲染模板的片段。這裡的意思是:找到“greet”模板,並查詢名為“content”的片段。)
如果您現在提交表單,並檢視瀏覽器開發者工具中的網路活動,您會看到伺服器只返回了更新內容所需的頁面片段。
另一種常見的用例是在頁面初次載入時從伺服器載入內容,甚至可以根據使用者的偏好進行定製。我們可以透過向我們希望觸發請求的元素新增 hx-get
屬性來使用 HTMX 實現這一點。我們可以嘗試修改 layout.html
模板中的 logo。不是靜態地包含圖片
<div class="row">
<div class="col-12">
<img src="../static/images/spring-logo.svg" th:src="@{/images/spring-logo.svg}" alt="Logo" style="width:200px;" loading="lazy">
</div>
</div>
我們可以使用一個佔位符
<div class="row">
<div class="col-12">
<span class="fa fa-spin fa-spinner" style="width:200px; text-align:center;">
</div>
</div>
然後讓 HTMX 動態載入它
<div class="row">
<div class="col-12" hx-get="/logo" hx-trigger="load">
<span class="fa fa-spin fa-spinner" style="width:200px; text-align:center;">
</div>
</div>
注意添加了 hx-get
和 hx-trigger
。hx-trigger
屬性告訴 HTMX 在頁面載入時觸發請求。預設是在點選時觸發。
hx-get
屬性告訴 HTMX 向伺服器發出 GET 請求以獲取該元素的內容。所以我們需要在 SampleController
中新增一個對映
@GetMapping(path = "/logo")
String logo() {
return "layout :: logo";
}
它僅僅渲染包含圖片的 layout.html
模板片段。layout.html
模板必須修改以包含 th:fragment
屬性
<div class="row" th:remove="all">
<div class="col-12" th:fragment="logo">
<img src="../static/images/spring-logo.svg" th:src="@{/images/spring-logo.svg}" alt="Logo"
style="width:200px;" loading="lazy">
</div>
</div>
注意,我們必須從模板中 th:remove
該片段,因為佔位符將在初始渲染時替換它。如果您現在執行該應用,您會看到當頁面載入時,旋轉器會被圖片替換。這將在瀏覽器開發者工具的網路活動中顯示。
HTMX 還有更多特性,我們在此沒有詳細介紹的空間。值得一提的是,有一個 Java 庫可以幫助實現這些特性,並且它也包含一些 Thymeleaf 工具:由 Wim Deblauwe 開發的 Spring Boot HTMX,可在 Maven Central 中作為依賴項使用。它可以使用自定義註解來匹配 hx-request
頭部,並且還可以幫助實現 HTMX 的其他特性。
還有其他與 HTMX 目標相似的庫,但它們的側重點和特性集不同。我們將介紹其中兩個。使用它們都很容易達到與 HTMX 相同的程度,但它們也有一些更復雜的特性,我們將留給您自行探索。
Unpoly 的 CDN 連結是
<script src='https://unpkg.com/unpoly/unpoly.min.js'></script>
示例程式碼中的 "unpoly" 分支像之前一樣使用了 Webjars。基本的(全頁渲染)表單提交示例如下所示
<div class="col-md-12">
<form th:action="@{/greet}" method="post" up-target="#content">
<input type="text" name="name" th:value="${name}"/>
<button type="submit" class="btn btn-primary">Greet</button>
</form>
</div>
<div id="content" class="col-md-12">
<span th:text="${greeting}">Hello, World</span><br/>
<span th:text="${time}">21:00</span>
</div>
所以 hx-target
變成了 up-target
,而 HTMX 的其餘修飾符在 Unpoly 中就是預設的。
要轉換為片段模板,我們需要遵循 HTMX 的模式:新增一個 th:fragment
和一個匹配 Unpoly 傳送的唯一頭部的控制器方法,例如 X-Up-Context
。
Hotwired Turbo 的 CDN 連結是
<script src='https://unpkg.com/@hotwired/turbo/dist/turbo.es2017-umd.js'></script>
示例程式碼中的 "turbo" 分支像之前一樣使用了 Webjars。基本的表單提交示例如下所示
<turbo-frame id="content">
<div class="col-md-12">
<form th:action="@{/greet}" method="post">
<input type="text" name="name" th:value="${name}" />
<button type="submit" class="btn btn-primary">Greet</button>
</form>
</div>
<div class="col-md-12">
<span th:text="${greeting}">Hello, World</span><br />
<span th:text="${time}">21:00</span>
</div>
</turbo-frame>
Turbo 沒有使用自定義屬性來標識表單處理互動,而是使用自定義元素(turbo-frame
)來標識將被替換的內容。表單的其餘部分保持不變。
要轉換為片段模板,我們需要向 <turbo-frame>
新增一個 th:fragment
宣告,並新增一個匹配 Turbo 傳送的唯一頭部的控制器方法,例如 Turbo-Frame
。
HTMX 非常專注於簡單的超媒體增強,雖然它已經發展到包含一些額外功能(主要是外掛),但它始終忠於其模擬下一代瀏覽器並儘可能保持功能集精簡的初衷。如果您喜歡這類內容,它在社交媒體上也非常有趣。另外兩個庫更具野心,涵蓋的範圍更廣,但它們與 HTMX 有足夠的共同之處,因此我們在此看到的示例非常相似。任何能夠生成 HTML 的伺服器端框架都可以與這些庫一起使用,它們可以用來增強瀏覽器體驗,而無需大型 JavaScript 框架。它們也非常適合像 Spring Boot 這樣的伺服器端框架,因為它們允許您使用伺服器來生成頁面內容和行為。模板最好在伺服器端使用支援片段的引擎進行渲染,因此 Thymeleaf 工作得很好,但也有其他選擇。當然,您也可以將 HTMX(及其相關庫)與一個完整的 JavaScript 框架一起使用,如果您喜歡,可以開始緩慢地用超媒體互動來替換框架元件。