领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多如今的前端开发主要由大型JavaScript客户端框架主导。这样做有很多很好的理由,但对于许多用例来说,它可能非常低效,而且框架工程变得极其复杂。在本文中,我想探索一种不同的方法,一种更高效、更灵活的方法,它由更小的构建块构成,并且非常适合服务器端应用程序框架,如Spring(或各种服务器端语言中的类似工具)。其理念是采用超媒体的概念,想象一下下一代浏览器将如何利用它,并使用少量JavaScript来增强当今的浏览器到那个级别。现代浏览器忽略HTML中的自定义元素和属性,但它们允许内容的作者使用JavaScript来定义其行为。已经有几个可用的库可以帮助实现这一点,我们将研究HTMX、Unpoly和Hotwired Turbo。我们还将研究如何将这些库与Spring Boot一起使用,以及如何将它们与传统的服务器端框架(如Thymeleaf)一起使用。
你可以在GitHub (dsyer/webmvc-thymeleaf)上找到源代码。“main”分支是起点,并且每个我们将要探索的库都有分支。
作为起点,我们将使用一个简单但并非微不足道的使用Thymeleaf的Spring Boot应用程序。它最初是为了对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将库加载到类路径中,这样也可以正常工作。Spring可以做一些额外的事情来帮助浏览器缓存库,它还可以帮助版本管理。
我们可以轻松添加的一个功能是使用HTMX提交表单而无需完全重新加载页面。我们可以通过向表单元素添加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将用结果替换表单的内容。
在本例中我们不希望这样做,因为该表单控制页面上不同(同级)元素中的某些内容。我们通过向表单元素添加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
模板中的徽标进行实验。而不是静态地包含图像
<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 工具:Spring Boot HTMX,由 Wim Deblauwe 提供,可在 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 框架一起使用,如果您喜欢它,您可以开始慢慢地用超媒体交互替换框架组件。