初次使用 htmx 时经常遇到的一个问题是:
"我需要更新屏幕上的其他内容。该如何实现?"
有多种方法可以实现这一点,在这个示例中我们将介绍其中几种。
我们将使用以下基本 UI 来讨论这个概念:一个简单的联系人表格,以及下方的表单,用于使用 hx-post 向表格添加新联系人。
<h2>联系人</h2>
<table class="table">
<thead>
<tr>
<th>姓名</th>
<th>邮箱</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table">
...
</tbody>
</table>
<h2>添加联系人</h2>
<form hx-post="/contacts">
<label>
姓名
<input name="name" type="text">
</label>
<label>
邮箱
<input name="email" type="email">
</label>
</form>
这里的问题是,当你在表单中提交一个新联系人时,你希望上面的联系人表格刷新并包含刚刚通过表单添加的联系人。
我们有哪些解决方案?
这里最简单的解决方案是"扩展目标",将表单的目标扩大到包含表格和表单。例如,你可以将整个内容包裹在一个 div
中,然后在表单中定位这个 div
:
<div id="table-and-form">
<h2>联系人</h2>
<table class="table">
<thead>
<tr>
<th>姓名</th>
<th>邮箱</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table">
...
</tbody>
</table>
<h2>添加联系人</h2>
<form hx-post="/contacts" hx-target="#table-and-form">
<label>
姓名
<input name="name" type="text">
</label>
<label>
邮箱
<input name="email" type="email">
</label>
</form>
</div>
注意,我们使用 hx-target 属性定位了包裹的 div。你需要在响应 POST
到 /contacts
时渲染表格和表单。
这是一个简单可靠的方法,尽管它可能感觉不够优雅。
一个更复杂的方法是使用 带外交换 将更新的内容交换到 DOM 中。
使用这种方法,HTML 不需要从原始设置中做任何更改:
<h2>联系人</h2>
<table class="table">
<thead>
<tr>
<th>姓名</th>
<th>邮箱</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table">
...
</tbody>
</table>
<h2>添加联系人</h2>
<form hx-post="/contacts">
<label>
姓名
<input name="name" type="text">
</label>
<label>
邮箱
<input name="email" type="email">
</label>
</form>
不需要修改前端内容,在响应 POST
到 /contacts
时,你可以包含一些额外的内容:
<tbody hx-swap-oob="beforeend:#contacts-table">
<tr>
<td>张三</td>
<td>zhang@example.com</td>
</tr>
</tbody>
<label>
姓名
<input name="name" type="text">
</label>
<label>
邮箱
<input name="email" type="email">
</label>
这段内容使用 hx-swap-oob 属性将自己追加到 #contacts-table
,在成功添加联系人后更新表格。
一个更复杂的方法是在成功创建联系人时触发客户端事件,然后在表格上监听该事件,使表格刷新。
<h2>联系人</h2>
<table class="table">
<thead>
<tr>
<th>姓名</th>
<th>邮箱</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table" hx-get="/contacts/table" hx-trigger="newContact from:body">
...
</tbody>
</table>
<h2>添加联系人</h2>
<form hx-post"/contacts">
<label>
姓名
<input name="name" type="text">
</label>
<label>
邮箱
<input name="email" type="email">
</label>
</form>
我们添加了一个新的端点 /contacts/table
,用于重新渲染联系人表格。这个请求的触发器是一个自定义事件 newContact
。我们在 body
上监听这个事件,因为当表单响应触发它时,由于事件冒泡,它最终会到达 body。
当 POST 到 /contacts
时成功创建联系人时,响应会包含一个 HX-Trigger 响应头,如下所示:
HX-Trigger:newContact
这将触发表格向 /contacts/table
发出 GET
请求,并渲染新添加的联系人行(以及表格的其余部分)。
非常清晰的事件驱动编程!
最后一个方法是使用 RESTful 路径依赖来刷新表格。htmx 的前身 Intercooler.js 在库中集成了 基于路径的依赖。
htmx 将其从核心功能中移除,但支持一个扩展 path deps,提供类似的功能。
更新我们的示例以使用该扩展需要加载扩展 JavaScript,然后像这样注释我们的 HTML:
<h2>联系人</h2>
<table class="table">
<thead>
<tr>
<th>姓名</th>
<th>邮箱</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table" hx-get="/contacts/table" hx-ext="path-deps" hx-trigger="path-deps" path-deps="/contacts">
...
</tbody>
</table>
<h2>添加联系人</h2>
<form hx-post="/contacts">
<label>
姓名
<input name="name" type="text">
</label>
<label>
姓名
<input name="email" type="email">
</label>
</form>
现在,当表单提交到 /contacts
URL 时,path-deps
扩展会检测到这一点,并在联系人表格上触发 path-deps
事件,从而触发请求。
这样做的好处是您无需对响应头做任何复杂处理。缺点是每次 POST
都会发出请求,即使联系人未成功创建。
通常我推荐第一种方法(扩展目标范围),特别是当需要更新的元素在 DOM 中位置相近时。这种方法简单可靠。
其次,自定义事件和 OOB 交换方法各有优势。我个人倾向于自定义事件方法,因为我喜欢事件驱动系统,但这只是个人偏好。您应根据自己的软件工程品味,以及哪种方法更符合您选择的服务器端技术来决定。
最后,path-deps 方法很有趣,如果它符合您的思维模式和整体系统架构,可以成为避免显式刷新的巧妙方式。但我建议最后考虑它,除非这个概念确实吸引您。