Back

使用 htmx SSE 扩展实现实时用户体验

使用 htmx SSE 扩展实现实时用户体验

大多数 Web 应用在某个时候都需要实时更新——通知徽章、进度条、无需轮询即可刷新的仪表板。通常的解决方案是 WebSockets 或完整的 SPA 框架。但如果你已经在使用 htmx,还有一条更简单的路径:htmx SSE 扩展,它可以将服务器发送事件直接连接到你的 HTML 中,几乎不需要任何 JavaScript。

核心要点

  • 服务器发送事件(SSE)通过标准 HTTP 提供单向的服务器到客户端流式传输——非常适合通知、仪表板和实时信息流。
  • htmx SSE 扩展是一个独立的包(htmx-ext-sse),它使用声明式属性将 SSE 流连接到你的 HTML,无需自定义 JavaScript。
  • 三个核心属性——sse-connectsse-swaphx-trigger="sse:<event>"——涵盖了大多数实时用户体验模式。
  • SSE 比 WebSockets 更易于部署,并消除了轮询的浪费请求,尽管它仅支持服务器到客户端的通信。

服务器发送事件的本质

服务器发送事件(SSE)是一种浏览器原生协议,用于通过标准 HTTP 连接进行单向的服务器到客户端流式传输。服务器保持连接打开,并在有内容时推送文本事件。浏览器通过 EventSource API 接收这些事件。

传输格式是纯文本:

event: priceUpdate
data: <li>BTC — $62,400</li>

每个事件都有一个可选的名称和一个 data 负载。多个 data: 行会被连接起来。事件之间用空行分隔。

因为 SSE 运行在 HTTP 之上,所以它可以通过代理和防火墙工作,无需特殊配置。它原生支持自动重连。权衡之处在于方向性:一旦连接打开,客户端无法向服务器发送消息。对于通知、仪表板、任务进度和实时信息流来说,这完全没问题。对于聊天或协作编辑,你需要使用 WebSockets。

安装 htmx SSE 扩展

SSE 支持并未内置于 htmx 核心中。它位于独立的 htmx-ext-sse 包中。加载这两个脚本并在容器元素上激活扩展:

<head>
  <script  src="https://cdn.jsdelivr.net/npm/htmx.org@latest/dist/htmx.min.js"></script>  
  <script  src="https://cdn.jsdelivr.net/npm/htmx-ext-sse@latest"></script>
</head>
<body hx-ext="sse">

对于基于 npm 的构建,使用 npm install htmx-ext-sse 安装,并在入口文件中导入 htmx.orghtmx-ext-sse

注意: 早期 htmx 版本中的旧 hx-sse 属性已被弃用。请使用 hx-ext="sse" 配合专用扩展。

htmx 流式更新的核心属性

三个属性涵盖了大多数实时用户体验模式:

属性用途
sse-connect="<url>"打开 EventSource 连接
sse-swap="<event-name>"将传入的 HTML 交换到元素中
hx-trigger="sse:<event-name>"当事件到达时触发 htmx 请求

一个在每次推送时替换自身内容的实时信息流:

<div hx-ext="sse" sse-connect="/feed" sse-swap="message">
  加载中…
</div>

服务器发送一个包含 data: <p>新项目</p> 的事件,后跟一个空行,htmx 就会替换 div 的内容——无需 JavaScript。

处理多个事件和触发请求

一个 sse-connect 可以为多个子元素提供数据,每个子元素监听不同的事件名称:

<div hx-ext="sse" sse-connect="/stream">
  <div sse-swap="statsUpdate"></div>
  <div sse-swap="alertBanner"></div>
</div>

你还可以使用 SSE 事件来触发后续 HTTP 请求,而不是直接交换内容。当事件表示有新数据可用,但你希望 htmx 获取完整的渲染片段时,这很有用:

<div hx-ext="sse" sse-connect="/events">
  <div hx-get="/notifications" hx-trigger="sse:newNotification">
    <!-- 在每个 SSE 事件上刷新 -->
  </div>
</div>

要在服务器发出完成信号时优雅地关闭流,添加 sse-close="done"——当名为 done 的事件到达时,连接就会关闭。

SSE 优于轮询或 WebSockets 的场景

  • 相比轮询: SSE 消除了浪费的请求。服务器仅在有变化时推送。
  • 相比 WebSockets: SSE 更易于部署,可以在 HTTP/1.1 和 HTTP/2 上工作,不需要特殊的服务器基础设施。仅在需要双向通信时才使用 WebSockets。

一个实用提示:HTTP/1.1 浏览器将每个域的连接数限制为六个。如果用户打开多个标签页,SSE 连接会争夺该限制。通过 HTTP/2 提供服务在很大程度上避免了这一限制,因为它通过单个连接复用多个流。

结论

htmx SSE 扩展让你能够添加真正的基于 HTML 传输的实时 UI——实时仪表板、进度指示器、通知流——只需几个 HTML 属性和一个知道如何保持连接打开的服务器端点。不需要状态管理库,不需要客户端路由,不需要构建流程。如果你的服务器可以流式传输文本,你的 UI 就可以是实时的。

常见问题

不可以。服务器发送事件严格是单向的,从服务器到客户端。如果你需要向服务器发送数据,可以将 SSE 流与标准 htmx 请求(使用 hx-post 或 hx-put)配对使用。对于完全双向通信,如实时聊天,WebSockets 是更好的选择。

浏览器内置的 EventSource API 会在短暂延迟后自动尝试重新连接。服务器可以通过在事件流中包含 retry 字段来控制重试间隔。htmx SSE 扩展继承了这种重连行为,无需你进行任何额外配置。

是的,实际上推荐使用 HTTP/2。在 HTTP/1.1 下,浏览器将每个域的并发连接限制为大约六个,因此具有打开 SSE 流的多个标签页可能会耗尽该限制。HTTP/2 通过单个连接复用流,有效地消除了这个上限。

发送一个与容器元素上 sse-close 属性值匹配的命名事件。例如,如果你将 sse-close 设置为 done,发送名为 done 的事件将导致扩展在客户端干净地关闭 EventSource 连接。

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay