↓推荐关注↓
开始一个新项目总是既令人兴奋又充满挑战。选择哪些技术来实现您的解决方案,需要对这些选择带来的成本加以权衡。
每增加一项技术,可能引入的问题和依赖项都可能导致进度逐渐放缓,甚至停滞不前。
前端是开发者经常感到决策疲劳的地方。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 负载更小。
前端负责使用 Java 将数据转换为演示性 HTML。这种模式绕过了客户端和渲染速度的限制,并在当时为访客提供了更出色的用户体验。
自那时以来,一些情况发生了变化:
对于 Htmx,在现代应用程序的上下文中,前端框架可能成为比它们声称要解决的问题更大的负担。
那么,Htmx 的运作方式有何不同?
Htmx 专注于声明式编程风格,允许您使用 Htmx 特定的特性装饰现有的 HTML 输出。这些特性为那些 HTML 元素提供了通常可能没有的更多功能。
所有 Htmx 的基本流程都包括以下几点:
我们看一个稍后我们将使用 Spring Boot 实现的简单 Htmx 示例。
< buttonhx-post= "/clicked"
hx-trigger= "click"
hx-target= "#parent-div"
hx-swap= "outerHTML"
>
Click Me!
button>
hx- 特性允许此按钮在每次点击时触发 HTTP POST。一旦服务器响应,我们将找到 #parent-div 并将其与生成的 HTML 交换。
这些特性不专属于任何 HTML 元素,可以组合使用来创造丰富的体验。例如,下面是一个当用户更改值时会触发请求的搜索框:
< inputtype= "text"name= "q"
hx-get= "/trigger_delay"
hx-trigger= "keyup changed delay:500ms"
hx-target= "#search-results"
placeholder= "Search..."
>
< divid= "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 类。这将是我们为第一个示例添加应用程序逻辑的地方。
packageorg.example.htmxdemo;
importorg.springframework.stereotype.Controller;
importorg.springframework.web.bind. annotation.GetMapping;
@Controller
publicclassHomeController{
@GetMapping( "/")
publicString home {
return"index";
}
}
接下来,我们在 resources/templates/index.html 下创建 index HTML 模板文件。确保粘贴以下内容。提供的 HTML 中的 head 标记中已包含依赖项,客户端将在页面呈现给用户时检索这些依赖项。
< htmlxmlns:th= "http://www.thymeleaf.org"lang= "en">
< head>
< title> Getting Started: Serving Web Content title>
< metahttp-equiv= "Content-Type"content= "text/html; charset=UTF-8"/>
< metaname= "viewport"content= "width=device-width, initial-scale=1"/>
< metaname= "color-scheme"content= "light dark"/>
< title> Htmx Demo title>
< linkrel= "stylesheet"href= "https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
< src= "https://unpkg.com/htmx.org@2.0.1"> >
head>
< body>
< mainclass= "container">
< section>
< h1> Htmx Demo h1>
< divid= "parent-div"> div>
< buttonhx-post= "/clicked"
hx-trigger= "click"
hx-target= "#parent-div"
hx-swap= "outerHTML">
Click Me!
button>
section>
main>
body>
html>
Htmx 是一个 no-build 库,这意味着您不需要任何额外的依赖项即可使用它。如您所注意到的,在我们模板的 head 元素中,我们只需要对 HTML 中库的 引用。此外,我还包含了一个 CSS 库 PicoCSS,以便使页面更加美观。根据开发环境的浅色/深色模式设置,您的输出可能略有不同。
最终,您需要下载并存储所有第三方文件与代码以用于生产设置。
接下来,返回到 HomeController 并实现 /clicked 端点。记住,这需要使用 POST HTTP 方法进行处理。使用适当的 HTTP 方法来处理交互对 Htmx 开发至关重要。通常,使用 GET 进行不可变调用,使用 POST、PUT 和 DELETE 进行可变调用。
packageorg.example.htmxdemo;
importorg.springframework.stereotype.Controller;
importorg.springframework.ui.Model;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.PostMapping;
importjava.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/ 中的其他模板文件旁边。
< htmlxmlns= "http://www.w3.org/1999/xhtml"
xmlns:th= "http://www.thymeleaf.org"lang= "en">
< head>
< title> fragments title>
head>
< body>
< divth:fragment= "result"id= "parent-div">
< pth:text= "${now}"> p>
div>
body>
html>
运行我们的应用程序,我们现在可以点击页面上的按钮并实时查看界面更新。
恭喜。您已成功处理了即将到来的许多 Htmx 请求中的第一个!
现在,让我们为更复杂的场景实现该搜索文本框。
Spring Boot 中由 Htmx
提供支持的搜索
我们将向示例中添加一个新的搜索功能,实现之前展示的代码段。
首先,更新 HTML 代码段以包括搜索用户界面。在 index.html 中,更新内容以匹配以下代码:
< htmlxmlns:th= "http://www.thymeleaf.org"lang= "en">
< head>
< title> Getting Started: Serving Web Content title>
< metahttp-equiv= "Content-Type"content= "text/html; charset=UTF-8"/>
< metaname= "viewport"content= "width=device-width, initial-scale=1"/>
< metaname= "color-scheme"content= "light dark"/>
< title> Htmx Demo title>
< linkrel= "stylesheet"href= "https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
< src= "https://unpkg.com/htmx.org@2.0.1"> >
head>
< body>
< mainclass= "container">
< section>
< h1> Htmx Demo h1>
< divid= "parent-div"> div>
< buttonhx-post= "/clicked"
hx-trigger= "click"
hx-target= "#parent-div"
hx-swap= "outerHTML">
Click Me!
button>
section>
< section>
< inputtype= "text"
name= "q"
hx-get= "/search"
hx-trigger= "keyup changed delay:500ms"
hx-target= "#search-results"
placeholder= "Search..."
>
< divth:replace= "search::results">
div>
section>
main>
body>
html>
在 resources/templates/ 中创建一个新的 search.html 文件,然后将以下内容复制到新创建的文件中。
< htmlxmlns= "http://www.w3.org/1999/xhtml"
xmlns:th= "http://www.thymeleaf.org"lang= "en">
< head>
< title> fragments title>
head>
< body>
< divid= "search-results"th:fragment= "results">
< ulth:each= "result: ${results}">
< lith:text= "${result}"> li>
ul>
div>
body>
html>
此文件包含我们的响应片段,它将显示用户发起的搜索的结果。我们来最后一次更新 HomeController:
packageorg.example.htmxdemo;
importorg.springframework.stereotype.Controller;
importorg.springframework.ui.Model;
importorg.springframework.web.bind. annotation.GetMapping;
importorg.springframework.web.bind. annotation.PostMapping;
importjava.time.LocalDateTime;
importjava.util.List;
@Controller
publicclassHomeController{
static List searchResults =
List.of( "one", "two", "three", "four", "five");
@GetMapping( "/")
publicString home(Model model) {
model.addAttribute( "results", searchResults);
return"index";
}
@GetMapping( "/search")
publicString search(String q, Model model) {
varfiltered = searchResults
.stream
.filter(s -> s.startsWith(q.toLowerCase))
.toList;
model.addAttribute( "results", filtered);
return"search :: results";
}
@PostMapping( "/clicked")
publicString clicked(Model model) {
model.addAttribute( "now", LocalDateTime.now.toString);
return"clicked :: result";
}
}
我们停下来思考一下我们在 HomeController 类中执行的操作。
所有这些逻辑均基于我们对 Spring Boot 和 Thymeleaf 的了解实现。这真是太神奇了。重新运行应用程序并开始在搜索框中输入以查看筛选后的结果。
可能看起来不太像,但您已经实现了一些复杂的 Htmx 场景。
除此之外,还有更多内容需要学习,而本文介绍的基础知识是一个绝佳起点。
社区参考
Htmx 拥有一个不断壮大的社区,社区的开发者充满热情,我们希望与您分享他们的一些作品。了解社区对 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,而其他页面则完全不提及它。
这可以显著加快您的交付速度,因为您可以减少与 Java 构建工具打交道的时间,而将更多时间用于构建用户喜爱的解决方案。
我们希望您喜欢这篇文章。我们很乐意听到您集成 Htmx 和 Spring Boot 的经验。如果您有任何问题或反馈,请随时评论。
本博文英文原作者:Khalid Abuhakmeh
延伸阅读
前端又叠buff!拥有近4万star的超级HTML增强工具:HTMX
END