htmx 4:用 Fetch、Morphing Swap 和显式继承继续降低前端复杂度

2026-09-18 15 预计阅读时间: 1 分钟
来源: infoq.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

htmx 4 的核心变化,不是增加一套更复杂的前端运行时,而是重新整理了几个影响实际开发体验的底层能力:从 XMLHttpRequest 转向 fetch()、增强流式响应、内置 morphing swap、引入 hx-partial,并把属性继承改为显式行为。对于偏好服务端渲染和少量 JavaScript 的团队,这些变化会直接影响请求、更新 DOM 以及维护局部状态的方式。

从 XMLHttpRequest 转向 Fetch

fetch() 已经成为现代浏览器处理 HTTP 请求的标准 API。htmx 4 将请求基础迁移到 Fetch,意味着库可以更自然地利用流式响应、标准化的请求配置和更现代的浏览器能力。

对应用开发者来说,最重要的变化不是手动调用 fetch(),而是 htmx 的请求模型更适合逐步接收服务端输出。长列表、日志窗口、服务器推送的局部结果,都可以围绕流式传输设计,而不必等整个响应生成完毕后再替换页面。

可以先用原生 Fetch 验证服务端是否支持流式响应。下面的示例会持续读取响应体,并把每个文本片段追加到日志区域。它不依赖任何构建工具,保存为 HTML 后即可改造:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Fetch stream demo</title>
</head>
<body>
  <button id="start">Start stream</button>
  <pre id="output"></pre>

  <script>
    const button = document.querySelector('#start');
    const output = document.querySelector('#output');

    button.addEventListener('click', async () => {
      button.disabled = true;
      output.textContent = '';

      try {
        const response = await fetch('/api/progress');
        if (!response.ok || !response.body) {
          throw new Error(`HTTP ${response.status}`);
        }

        const reader = response.body.getReader();
        const decoder = new TextDecoder();

        while (true) {
          const { value, done } = await reader.read();
          if (done) break;
          output.textContent += decoder.decode(value, { stream: true });
        }
      } catch (error) {
        output.textContent += `\nRequest failed: ${error.message}`;
      } finally {
        button.disabled = false;
      }
    });
  </script>
</body>
</html>

运行这个页面前,需要让 /api/progress 返回分块响应。例如,服务端可以按行写入内容,而不是一次性构造完整响应。实际项目中还要确认代理服务器没有缓冲响应,并设置合适的超时时间。

Morphing Swap 让 DOM 更新更细腻

传统的 HTML 局部更新通常会把目标元素的 innerHTML 或整个节点替换掉。这种方式简单,但可能丢失输入框焦点、光标位置、展开状态以及其他由浏览器或客户端代码维护的 DOM 状态。

Morphing swap 的思路是比较旧 DOM 和新 HTML,再尽量复用已有节点。这样,服务端仍然可以返回完整、清晰的 HTML,浏览器端则有机会保留用户正在进行的交互状态。它特别适合以下场景:

  • 用户正在编辑的表单局部刷新。
  • 可展开的列表或树形菜单重新加载。
  • 带有焦点、选中项或滚动位置的结果区域更新。
  • 服务端重新渲染后仍希望保留节点身份的组件。

可以这样设计一个 htmx 4 风格的局部更新区域。示例中的 hx-swap="morph" 表达使用 morphing swap;具体属性名称和兼容细节应以实际版本文档为准:

<section id="profile-panel">
  <form
    hx-put="/profile"
    hx-target="#profile-panel"
    hx-swap="morph"
  >
    <label>
      Display name
      <input name="display_name" value="Ada" autocomplete="name">
    </label>

    <label>
      Time zone
      <select name="timezone">
        <option value="UTC">UTC</option>
        <option value="Asia/Shanghai" selected>Asia/Shanghai</option>
      </select>
    </label>

    <button type="submit">Save</button>
  </form>
</section>

这里的关键不在于把所有更新都改成 morphing,而是明确哪些区域需要保留 DOM 状态。纯展示内容、不会被用户交互的区域,普通替换通常更容易理解,也更容易排查问题。Morphing 算法并不能自动解决所有状态冲突,第三方组件、重复 ID 和强依赖节点身份的代码仍然需要测试。

hx-partial 与更清晰的响应边界

hx-partial tag 旨在让服务端返回的局部内容边界更明确。过去,开发者经常依赖目标选择器约定响应必须包含哪些片段;当页面变大、模板变多时,响应的职责容易变得模糊。

实践中,可以把一个请求的响应限制在明确的局部模板内:列表请求只返回列表片段,编辑请求只返回编辑区域。这样做有两个好处:服务端模板更容易复用,前端也更容易判断一次请求会影响什么内容。

一个可改造的结构如下:

<div id="orders">
  <button
    hx-get="/orders?page=2"
    hx-target="#orders"
    hx-swap="morph"
  >
    Load next page
  </button>
</div>

<!-- The server response is deliberately limited to this partial region. -->
<hx-partial id="orders">
  <ol>
    <li>Order #1042 - shipped</li>
    <li>Order #1041 - processing</li>
  </ol>
</hx-partial>

上面的响应结构是实践示意。接入项目时,应根据 htmx 4 的实际解析规则调整标签用法,并确保服务端模板不会在同一个响应中混入无关页面结构。

显式属性继承,减少隐式影响

属性继承很方便:在父元素上设置请求地址、目标区域或触发行为,子元素可以自动使用这些配置。但隐式继承也会制造维护成本,尤其是组件被复制到另一个页面后,开发者很难从局部代码判断它为什么会发起某个请求。

htmx 4 将属性继承改为显式行为后,团队需要更认真地定义组件边界。建议把共享配置放在真正稳定的容器上,并在跨组件复用时明确声明继承关系。不要依赖“页面上某个更高层的元素碰巧设置了属性”。

同时,事件名称标准化会降低不同模块之间的沟通成本。项目可以把事件命名当作接口管理:统一大小写和命名风格,记录哪些事件由服务端响应触发,哪些事件由用户操作触发。

升级时的检查清单

升级到 htmx 4 前,可以按以下顺序检查:

  1. 搜索项目中依赖 XMLHttpRequest 行为的扩展、拦截器和测试桩。
  2. 为流式响应增加代理缓冲、断连和超时测试。
  3. 只对需要保留焦点或节点状态的区域启用 morphing swap。
  4. 检查模板是否清楚区分完整页面和局部响应。
  5. 显式梳理父子元素之间的属性继承关系。
  6. 统一事件名称,并更新自动化测试中的事件断言。
  7. 在真实表单、键盘操作和浏览器后退场景中验证 DOM 状态是否保留。

htmx 4 的方向仍然很克制:把更多能力放进 HTML 请求和响应流程,同时减少应用层 JavaScript 的数量。它并不意味着所有页面都应该放弃组件框架,而是为服务端渲染、渐进增强和局部交互提供了一条更完整的路径。采用时应从请求边界和 DOM 状态入手,逐块迁移,而不是一次性重写整个前端。


相关推荐