IntelliJ IDEA
IntelliJ IDEA – the Leading Java and Kotlin IDE, by JetBrains
面向 Spring Boot 开发者的 Htmx 的简介
开始一个新项目总是既令人兴奋又充满挑战。 选择哪些技术来实现您的解决方案,需要对这些选择带来的成本加以权衡。 每增加一项技术,可能引入的问题和依赖项都可能导致进度逐渐放缓,甚至停滞不前。 前端是开发者经常感到决策疲劳的地方。 React、Angular 和 Vue 等前端框架的流行确实带来了许多价值,但也在工具、安全考量因素、网络流量和巨大的初始负载方面产生了代价高昂的权衡。 如果您在为下一个项目做出前端决策时感到不知所措,那么这篇文章非常适合您。
在这篇文章中,我们将探索一个名为 Htmx 的新兴库,它允许您利用现有的 Spring Boot 知识来提供交互式用户体验,同时避免您在使用其他前端框架时可能遇到的部分挫折。 阅读完这篇文章后,您应该对将 Htmx 添加到新项目或现有 Spring Boot 项目中充满信心。
什么是 Htmx?
要理解什么是 Htmx,我们必须首先从哲学上理解这个库试图实现的目标。
Htmx 毫不掩饰地以加入超媒体阵营为荣。 超媒体是 Ted Nelson 于 1965 年创造的术语,它专注于单个文档可能包含多个交互式元素(如文本、图像、视频,以及其他文档的链接)这样一种观点。 如果这听起来像 HTML,那您猜对了。 HTML 就是这一概念的典型示例,并已成功实现了我们今天熟知的互联网。 然后,在 2015 年,Web 开发的格局发生了变化。
在 Web 2.0 时代,Web 开发开始将 Web 体验的 UI 元素分为前端和后端。 后端 API 提供 JSON 或 XML,因为这些负载比完整的 HTML 负载更小。 前端负责使用 JavaScript 将数据转换为演示性 HTML。 这种模式绕过了客户端和渲染速度的限制,并在当时为访客提供了更出色的用户体验。 自那时以来,一些情况发生了变化:
- 互联网的整体速度显著提高
- 客户端在渲染 HTML 方面效率更高
- JavaScript 和 JSON 的负载已经膨胀
- JavaScript 工具变得越来越复杂
对于 Htmx,在现代应用程序的上下文中,前端框架可能成为比它们声称要解决的问题更大的负担。 那么,Htmx 的运作方式有何不同?
Htmx 专注于声明式编程风格,允许您使用 Htmx 特定的特性装饰现有的 HTML 输出。 这些特性为那些 HTML 元素提供了通常可能没有的更多功能。 所有 Htmx 的基本流程都包括以下几点:
- 引发事件的客户端触发器
- 事件通常触发对服务器后端的 Web 请求
- 服务器以 HTML 片段响应
- Htmx 将现有的 DOM 元素替换为响应
我们看一个稍后我们将使用 Spring Boot 实现的简单 Htmx 示例。
<button hx-post="/clicked" hx-trigger="click" hx-target="#parent-div" hx-swap="outerHTML" > Click Me! </button>
hx-
特性允许此按钮在每次点击时触发 HTTP POST。 一旦服务器响应,我们将找到 #parent-div
并将其与生成的 HTML 交换。
这些特性不专属于任何 HTML 元素,可以组合使用来创造丰富的体验。 例如,下面是一个当用户更改值时会触发请求的搜索框:
<input type="text" name="q" hx-get="/trigger_delay" hx-trigger="keyup changed delay:500ms" hx-target="#search-results" placeholder="Search..." > <div id="search-results"></div>
这一特定示例还定义了在向服务器发出请求前的 500ms 延迟,以避免在用户仍在输入时发送请求,让服务器只收到最相关的搜索查询,而不是全部输入 – 这种技术称为“去抖动”。
现在,您对 Htmx 有了大致的了解,我们将它添加到一个 Spring Boot 示例项目中并实现两个代码段的后端。
Spring Boot 中的首个 Htmx 体验
开始之前,我建议为 JetBrains IDE 安装 Htmx 支持插件。 这将极大提升您的 Htmx 开发体验。 感谢 Hugo Mesquita!
在 IntelliJ IDEA 中使用 New Project(新建项目)对话框创建一个新的 Spring Boot 项目。 如果您已经有一个 Spring Boot 项目,请跳过此步骤。
在下一个屏幕上,选择 Spring Web 和 Thymeleaf 依赖项。
首先,我们创建一个新的 HomeController
类。 这将是我们为第一个示例添加应用程序逻辑的地方。
package org.example.htmxdemo; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class HomeController { @GetMapping("/") public String home() { return "index"; } }
接下来,我们在 resources/templates/index.html
下创建 index
HTML 模板文件。 确保粘贴以下内容。 提供的 HTML 中的 head
标记中已包含依赖项,客户端将在页面呈现给用户时检索这些依赖项。
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org" lang="en"> <head> <title>Getting Started: Serving Web Content</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="color-scheme" content="light dark" /> <title>Htmx Demo</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" > <script src="https://unpkg.com/htmx.org@2.0.1"></script> </head> <body> <main class="container"> <section> <h1>Htmx Demo</h1> <div id="parent-div"></div> <button hx-post="/clicked" hx-trigger="click" hx-target="#parent-div" hx-swap="outerHTML"> Click Me! </button> </section> </main> </body> </html>
Htmx 是一个 no-build 库,这意味着您不需要任何额外的依赖项即可使用它。 如您所注意到的,在我们模板的 head
元素中,我们只需要对 HTML 中库的 script
引用。 此外,我还包含了一个 CSS 库 PicoCSS,以便使页面更加美观。 根据开发环境的浅色/深色模式设置,您的输出可能略有不同。
最终,您需要下载并存储所有第三方文件与代码以用于生产设置。
接下来,返回到 HomeController
并实现 /clicked
端点。 记住,这需要使用 POST
HTTP 方法进行处理。 使用适当的 HTTP 方法来处理交互对 Htmx 开发至关重要。 通常,使用 GET
进行不可变调用,使用 POST
、PUT
和 DELETE
进行可变调用。
package org.example.htmxdemo; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import java.time.LocalDateTime; @Controller public class HomeController { @GetMapping("/") public String home() { return "index"; } @PostMapping("/clicked") public String clicked(Model model) { model.addAttribute("now", LocalDateTime.now().toString()); return "clicked :: result"; } }
最后,让我们在 clicked.html
中实现 HTML 片段,随后将其放置在 resources/templates/
中的其他模板文件旁边。
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" lang="en"> <head> <title>fragments</title> </head> <body> <div th:fragment="result" id="parent-div"> <p th:text="${now}"></p> </div> </body> </html>
运行我们的应用程序,我们现在可以点击页面上的按钮并实时查看界面更新。
恭喜。 您已成功处理了即将到来的许多 Htmx 请求中的第一个!
现在,让我们为更复杂的场景实现该搜索文本框。
Spring Boot 中由 Htmx 提供支持的搜索
我们将向示例中添加一个新的搜索功能,实现之前展示的代码段。 首先,更新 HTML 代码段以包括搜索用户界面。 在 index.html
中,更新内容以匹配以下代码:
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org" lang="en"> <head> <title>Getting Started: Serving Web Content</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="color-scheme" content="light dark" /> <title>Htmx Demo</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" > <script src="https://unpkg.com/htmx.org@2.0.1"></script> </head> <body> <main class="container"> <section> <h1>Htmx Demo</h1> <div id="parent-div"></div> <button hx-post="/clicked" hx-trigger="click" hx-target="#parent-div" hx-swap="outerHTML"> Click Me! </button> </section> <section> <input type="text" name="q" hx-get="/search" hx-trigger="keyup changed delay:500ms" hx-target="#search-results" placeholder="Search..." > <div th:replace="search::results"> </div> </section> </main> </body> </html>
在 resources/templates/
中创建一个新的 search.html
文件,然后将以下内容复制到新创建的文件中。
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" lang="en"> <head> <title>fragments</title> </head> <body> <div id="search-results" th:fragment="results"> <ul th:each="result: ${results}"> <li th:text="${result}"></li> </ul> </div> </body> </html>
此文件包含我们的响应片段,它将显示用户发起的搜索的结果。 我们来最后一次更新 HomeController
:
package org.example.htmxdemo; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import java.time.LocalDateTime; import java.util.List; @Controller public class HomeController { static List searchResults = List.of("one", "two", "three", "four", "five"); @GetMapping("/") public String home(Model model) { model.addAttribute("results", searchResults); return "index"; } @GetMapping("/search") public String search(String q, Model model) { var filtered = searchResults .stream() .filter(s -> s.startsWith(q.toLowerCase())) .toList(); model.addAttribute("results", filtered); return "search :: results"; } @PostMapping("/clicked") public String clicked(Model model) { model.addAttribute("now", LocalDateTime.now().toString()); return "clicked :: result"; } }
我们停下来思考一下我们在 HomeController
类中执行的操作。
- 我们的
home
方法为初始体验设置搜索结果。 - 我们的
index.html
使用search :: results
片段 - 当用户输入时,
/search
端点处理查询并返回修改后的search :: results
片段与我们的新结果。
所有这些逻辑均基于我们对 Spring Boot 和 Thymeleaf 的了解实现。 这真是太神奇了。 重新运行应用程序并开始在搜索框中输入以查看筛选后的结果。
可能看起来不太像,但您已经实现了一些复杂的 Htmx 场景。 除此之外,还有更多内容需要学习,而本文介绍的基础知识是一个绝佳起点。
社区参考
Htmx 拥有一个不断壮大的社区,社区的开发者充满热情,我们希望与您分享他们的一些作品。 了解社区对 Htmx 的热情可能有助于减轻您对采用这项技术的焦虑。
- 官方 Htmx 网站和文档 – https://htmx.org/
- Josh Long – HTMX 和 Spring Boot
- SivaLabs – Spring Boot Thymeleaf HTMX 教程
- Dan Vega – 在 Spring Boot 中使用 Thymeleaf 轻松上手 HTMX
- Will Iverson –Spring Boot + HTMX = 轻松实现全栈
- Wim Deblauwe – Spring 提示:HTMX
如果想回顾后端技术,另请阅读我的指南系列文章“面向 ASP.NET Core 开发者的 Htmx”,其中包含许多可以根据 Spring Boot 进行调整的示例和技术。
对于认真投入 Htmx 开发的人,请查看适用于 Htmx 的 Spring Boot 和 Thymeleaf 库,它为您的 Spring Boot 应用程序添加了有用的元素。
另外,要获取本文中展示的示例代码,技术布道师 Marit van Djik 将一个完整示例推送到她的 GitHub 仓库。
结论
如果您深受前端的困扰,并且更喜欢使用像 Spring Boot 和 Thymeleaf 这样的后端工具,您可以考虑在下一个解决方案中使用 Htmx。 在我看来,Htmx 的一个最大卖点是您可以逐步将其分层。 您的 Web 应用程序中的一些页面可能会大量使用 Htmx,而其他页面则完全不提及它。 这可以显著加快您的交付速度,因为您可以减少与 JavaScript 构建工具打交道的时间,而将更多时间用于构建用户喜爱的解决方案。
我们希望您喜欢这篇文章。 我们很乐意听到您集成 Htmx 和 Spring Boot 的经验。 如果您有任何问题或反馈,请随时评论。
本博文英文原作者: