开始一个新项目总是既令人兴奋又充满挑战。选择哪些技术来实现您的解决方案,需要对这些选择带来的成本加以权衡。每增加一项技术,可能引入的问题和依赖项都可能导致进度逐渐放缓,甚至停滞不前。前端是开发者经常感到决策疲劳的地方。React、Angular 和 Vue 等前端框架的流行确实带来了许多价值,但也在工具、安全考量因素、网络流量和巨大的初始负载方面产生了代价高昂的权衡。如果您在为下一个项目做出前端决策时感到不知所措,那么这篇文章非常适合您。
在这篇文章中,我们将探索一个名为 Htmx 的新兴库,它允许您利用现有的 Spring Boot 知识来提供交互式用户体验,同时避免您在使用其他前端框架时可能遇到的部分挫折。阅读完这篇文章后,您应该对将 Htmx 添加到新项目或现有 Spring Boot 项目中充满信心。
什么是 Htmx?
要理解什么是 Htmx,我们必须首先从哲学上理解这个库试图实现的目标。
Htmx 毫不掩饰地以加入超媒体阵营为荣。超媒体(Hypermedia)是 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/[email protected]"></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/[email protected]"></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/
如果想回顾后端技术,另请阅读我的指南系列文章“面向 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 的经验。如果您有任何问题或反馈,请随时评论。
本博文英文原作者:Khalid Abuhakmeh
先行体验 JetBrains AI Assistant
IntelliJ IDEA 相关阅读
⏬ 戳「阅读原文」了解更多
本文分享自微信公众号 - JetBrains(JetBrainsChina)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。