IntelliJ IDEA Java

面向 Spring Boot 开发者的 Htmx 的简介

Read this post in other languages:

开始一个新项目总是既令人兴奋又充满挑战。 选择哪些技术来实现您的解决方案,需要对这些选择带来的成本加以权衡。 每增加一项技术,可能引入的问题和依赖项都可能导致进度逐渐放缓,甚至停滞不前。 前端是开发者经常感到决策疲劳的地方。 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 WebThymeleaf 依赖项。

首先,我们创建一个新的 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 进行不可变调用,使用 POSTPUTDELETE 进行可变调用。

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 类中执行的操作。

  1. 我们的 home 方法为初始体验设置搜索结果。
  2. 我们的 index.html 使用 search :: results 片段
  3. 当用户输入时,/search 端点处理查询并返回修改后的 search :: results 片段与我们的新结果。

所有这些逻辑均基于我们对 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,而其他页面则完全不提及它。 这可以显著加快您的交付速度,因为您可以减少与 JavaScript 构建工具打交道的时间,而将更多时间用于构建用户喜爱的解决方案。

我们希望您喜欢这篇文章。 我们很乐意听到您集成 Htmx 和 Spring Boot 的经验。 如果您有任何问题或反馈,请随时评论。

本博文英文原作者:

Khalid Abuhakmeh

Khalid Abuhakmeh

image description

Discover more