人们在首次使用 htmx 时经常会遇到这样一个问题:
“我需要更新屏幕上的其他内容。该怎么做?”
有多种方法可以实现这一点,在本示例中,我们将逐步介绍其中一些方法。
我们将使用以下基本 UI 来讨论这个概念:一个简单的联系人表格,以及表格下方用于使用 hx-post 添加新联系人到表格的表单。
<h2>Contacts</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table">
...
</tbody>
</table>
<h2>Add A Contact</h2>
<form hx-post="/contacts">
<label>
Name
<input name="name" type="text">
</label>
<label>
Email
<input name="email" type="email">
</label>
</form>
这里的问题是,当您在表单中提交新联系人时,您希望上面的联系人表格刷新并包含刚刚由表单添加的联系人。
我们有哪些解决方案?
这里最简单的解决方案是将表单的目标“扩展”以包围表格 和 表单。例如,您可以将整个内容包装在一个 div 中,然后在表单中针对该 div:
<div id="table-and-form">
<h2>Contacts</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table">
...
</tbody>
</table>
<h2>Add A Contact</h2>
<form hx-post="/contacts" hx-target="#table-and-form">
<label>
Name
<input name="name" type="text">
</label>
<label>
Email
<input name="email" type="email">
</label>
</form>
</div>
注意,我们使用 hx-target 属性针对包围的 div。您需要在响应到 /contacts 的 POST 中渲染表格和表单。
这是一种简单可靠的方法,尽管它可能感觉不是特别优雅。
一种更复杂的方法是使用 out of band swaps 将更新的内容交换到 DOM 中。
使用这种方法,HTML 无需从原始设置更改:
<h2>Contacts</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table">
...
</tbody>
</table>
<h2>Add A Contact</h2>
<form hx-post="/contacts">
<label>
Name
<input name="name" type="text">
</label>
<label>
Email
<input name="email" type="email">
</label>
</form>
대신前端不需修改任何内容,在响应到 /contacts 的 POST 中,您将包含一些额外内容:
<tbody hx-swap-oob="beforeend:#contacts-table">
<tr>
<td>Joe Smith</td>
<td>joe@smith.com</td>
</tr>
</tbody>
<label>
Name
<input name="name" type="text">
</label>
<label>
Email
<input name="email" type="email">
</label>
此内容使用 hx-swap-oob 属性将自身附加到 #contacts-table,在联系人添加成功后更新表格。
一种更复杂的方法是在成功创建联系人时触发客户端事件,然后在表格上监听该事件,从而导致表格刷新。
<h2>Contacts</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table" hx-get="/contacts/table" hx-trigger="newContact from:body">
...
</tbody>
</table>
<h2>Add A Contact</h2>
<form hx-post="/contacts">
<label>
Name
<input name="name" type="text">
</label>
<label>
Email
<input name="email" type="email">
</label>
</form>
我们添加了一个新端点 /contacts/table 来重新渲染联系人表格。此请求的触发器是一个自定义事件 newContact。我们在 body 上监听此事件,因为当表单响应触发它时,由于事件冒泡,它最终会到达 body。
当在向 /contacts 的 POST 中成功创建联系人时,响应包含一个 HX-Trigger 响应头,看起来像这样:
HX-Trigger:newContact
这将触发表格向 /contacts/table 发出 GET,并渲染新添加的联系人行(以及表格的其余部分)。
非常干净的事件驱动编程!
最后一种方法是使用 REST-ful 路径依赖来刷新表格。htmx 的前身 Intercooler.js 将 path-based dependencies 集成到库中。
htmx 将其作为核心功能移除,但支持一个扩展 path deps,它提供类似功能。
更新我们的示例以使用该扩展将涉及加载扩展 JavaScript,然后像这样注释我们的 HTML:
<h2>Contacts</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</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>Add A Contact</h2>
<form hx-post="/contacts">
<label>
Name
<input name="name" type="text">
</label>
<label>
Email
<input name="email" type="email">
</label>
</form>
现在,当表单发布到 /contacts URL 时,path-deps 扩展将检测到它并在联系人表格上触发 path-deps 事件,从而触发请求。
这里的优势是您无需对响应头做任何复杂处理。缺点是每次 POST 都会发出请求,即使联系人未成功创建。
通常,我推荐第一种方法,即扩展您的目标,特别是如果需要更新的元素在 DOM 中彼此相对较近。它简单可靠。
之后,我认为自定义事件和 OOB 交换方法各有千秋。我倾向于自定义事件方法,因为我喜欢事件导向系统,但这是个人偏好。您选择哪一种应由您自己的软件工程品味以及哪一种与您选择的服务器端技术更好地匹配来决定。
最后,path-deps 方法很有趣,如果它与您的心理模型和整体系统架构非常契合,它可以是一种有趣的方式来避免显式刷新。不过,除非这个概念真正吸引您,否则我会最后考虑它。