更新其他内容

初次使用 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>

这里的问题是,当你在表单中提交一个新联系人时,你希望上面的联系人表格刷新并包含刚刚通过表单添加的联系人。

我们有哪些解决方案?

解决方案 1:扩展目标

这里最简单的解决方案是"扩展目标",将表单的目标扩大到包含表格和表单。例如,你可以将整个内容包裹在一个 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 时渲染表格和表单。

这是一个简单可靠的方法,尽管它可能感觉不够优雅。

解决方案 2:带外响应

一个更复杂的方法是使用 带外交换 将更新的内容交换到 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,在成功添加联系人后更新表格。

解决方案 3:触发事件

一个更复杂的方法是在成功创建联系人时触发客户端事件,然后在表格上监听该事件,使表格刷新。

<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 请求,并渲染新添加的联系人行(以及表格的其余部分)。

非常清晰的事件驱动编程!

解决方案 4:使用路径依赖扩展

最后一个方法是使用 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 方法很有趣,如果它符合您的思维模式和整体系统架构,可以成为避免显式刷新的巧妙方式。但我建议最后考虑它,除非这个概念确实吸引您。