核心概念速览
<a href> 和 <form action> 来发现下一步可以做什么。这就是 Web 浏览器几十年来的工作方式。htmx 将这个原则延伸到 AJAX 请求场景。hx-get、hx-post、hx-target 等)扩展 HTML 的超媒体能力。核心思想:服务器返回 HTML 片段,htmx 将其插入到 DOM 指定位置,而不是服务器返回 JSON、前端 JavaScript 渲染。<a>(发出 GET 请求)和表单 <form>(发出 GET/POST 请求)。原生 HTML 的超媒体能力非常有限;htmx 把这种能力扩展到任意 HTML 元素和任意 HTTP 方法。Web 的原始设计
Roy Fielding 与 REST 论文
2000 年,Roy Fielding 在他的博士论文《架构风格与基于网络的软件架构设计》中描述了 Web 的架构风格——REST(Representational State Transfer,表述性状态转移)。其中一个关键约束是 HATEOAS(超媒体作为应用状态引擎):
客户端通过服务器返回的 HTML 中的超链接(<a href>、<form action>)来发现下一步可以做什么,而不是通过预先内置的知识。HTML 文档本身驱动应用的状态转换,这就是超媒体作为状态引擎的含义。
这就是 Web 最初的工作方式:浏览器请求一个 URL,服务器返回 HTML,HTML 中包含下一步操作的链接和表单,用户点击或提交,服务器处理并返回新的 HTML……整个应用流程完全由 HTML 驱动。
SPA 的兴起与代价
2010 年代初,Gmail 等 Single Page Application(SPA)证明了丰富的浏览器端交互体验的可能性。随后 Backbone.js、Angular、React、Vue 相继出现,SPA 成为现代 Web 开发的主流范式。
但 SPA 带来了显著的复杂度:
- 构建工具复杂:webpack/vite、babel、TypeScript、CSS-in-JS 的庞大工具链
- 状态管理复杂:Redux/Zustand/Pinia,前端需要维护大量本地状态
- 双倍工作:同样的业务逻辑在服务端(权限、验证)和客户端(UI 状态)都需要实现
- SEO 问题:客户端渲染导致首屏白屏,需要 SSR/SSG 再增加一层复杂度
- 体积膨胀:React 运行时 45KB+、Vue 22KB+,还未计算业务代码
Carson Gross 在 htmx 的官网 essay 中写道:"大多数 Web 应用不需要 SPA 的全部复杂性。如果我们把超媒体的能力还给 HTML,让 HTML 做它本来就应该做的事,很多应用可以极大地简化。"
htmx 的核心思想
扩展 HTML 而非替代 HTML
htmx 的设计哲学是:扩展 HTML 的能力,而不是替代 HTML。原生 HTML 有三个限制:
- 只有
<a>和<form>才能发出 HTTP 请求(其他元素不行) - 只有
GET和POST方法(PUT/DELETE/PATCH 无法在 HTML 中使用) - 请求总是替换整个页面(无法局部更新)
htmx 通过 HTML 属性消除了这三个限制:
- 任何元素都可以发出请求(通过
hx-get、hx-post等属性) - 任何 HTTP 方法都可以使用(
hx-put、hx-delete、hx-patch) - 响应用于局部更新指定的 DOM 区域(通过
hx-target和hx-swap)
服务器返回 HTML,而非 JSON
| 维度 | 传统 SPA 方式 | htmx 方式 |
|---|---|---|
| 服务器响应 | JSON 数据 | HTML 片段 |
| 前端职责 | 接收 JSON,渲染 UI,管理状态 | 发出请求,htmx 更新 DOM |
| 状态管理 | 前端 Store(Redux/Zustand) | 服务器是状态的唯一来源 |
| 模板引擎 | 前端 JSX/模板(React/Vue) | 服务端模板(Jinja2/Go tmpl) |
| JS 代码量 | 大量(框架 + 业务逻辑) | 极少或零 |
| 适合场景 | 高交互性应用(在线编辑器、游戏) | 内容驱动应用(博客、管理后台) |
安装 htmx
CDN 引入(最简单)
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/htmx.org@2.0.0"></script>
</head>
<body>
<!-- 所有 hx-* 属性现在都可用了 -->
<button
hx-get="/api/greeting"
hx-target="#result"
>
获取问候
</button>
<div id="result"></div>
</body>
</html>
npm 安装(与构建工具集成)
# npm
npm install htmx.org
# 在 JS 入口文件中引入
import 'htmx.org';
第一个 htmx 示例
完整示例:点击按钮加载内容
<!-- 前端 HTML -->
<button
hx-get="/api/users"
hx-target="#user-list"
hx-swap="innerHTML"
hx-trigger="click"
>
加载用户列表
</button>
<div id="user-list">
<p>点击按钮加载用户...</p>
</div>
# 服务器端(Python FastAPI)
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/api/users", response_class=HTMLResponse)
async def get_users():
# 返回 HTML 片段,不是 JSON!
users = await db_get_users()
html = "<ul>"
for user in users:
html += f'<li>{user.name} ({user.email})</li>'
html += "</ul>"
return html
htmx 发出的请求会携带 HX-Request: true 请求头。服务器可以通过检查这个头来区分完整页面请求和 htmx 局部请求,返回不同的响应(完整 HTML 页面 vs HTML 片段)。
htmx 请求生命周期
理解 htmx 请求的完整生命周期,有助于调试问题和利用事件系统:
每个阶段都可以通过 JavaScript 监听事件进行干预。例如:在 htmx:beforeRequest 中显示加载动画,在 htmx:afterSettle 中隐藏;在 htmx:beforeSwap 中允许 422 错误响应也更新 DOM;在 htmx:afterSwap 中重新初始化第三方库。
htmx 与 AJAX 的历史
htmx 并不是第一个用 HTML 属性实现 AJAX 的库。它的精神先祖是 intercooler.js(也是 Carson Gross 创建的),而更早则可以追溯到 2005 年 Jesse James Garrett 提出的 AJAX 技术。htmx 2.0 发布于 2024 年,移除了对 IE11 的支持,全面拥抱现代浏览器 API。
什么时候该用 htmx,什么时候不该用
htmx 适合的场景
- 内容管理系统、博客、文档站点
- 管理后台(CRUD 操作为主)
- 电商网站(商品列表、购物车)
- 社区/论坛(帖子列表、评论)
- 需要 SEO 的内容页面
- 小团队/全栈开发者,不想维护独立前端
- 现有 Server-Side Rendered 项目渐进增强
应该选 React/Vue 的场景
- 在线代码编辑器、白板、设计工具
- 复杂实时协作应用(Google Docs 类型)
- 游戏、动画密集型交互
- 离线优先的 PWA 应用
- 大量客户端状态(复杂表单联动)
- 已有大型前端团队和 React 基础设施
htmx 不适合高度交互的应用(在线编辑器、游戏、复杂仪表板)。如果用户操作需要立即的本地响应(不能等待服务器往返延迟)、需要大量客户端状态、或需要离线工作,React/Vue 是更合适的选择。选择 htmx 是一种架构权衡,而非信仰。
htmx 2.0 的关键变化
htmx 2.0 于 2024 年发布,相比 1.x 有几个重要变化:
- 移除 IE11 支持:使用现代浏览器 API,减少兼容性代码
- 扩展系统重构:SSE/WebSocket 移至独立扩展包(htmx-ext-sse、htmx-ext-ws)
- 默认行为调整:
hx-boost的行为有细微变化,升级时需注意 - 事件名称变化:部分内置事件重命名(如
htmx:afterRequest→htmx:afterRequest) - 安全增强:默认 CSRF token 支持更完善
htmx 的核心要点:① htmx 是对 HTML 的扩展,而非替代——它让任何 HTML 元素都能发起 AJAX 请求,实现局部页面更新;② HATEOAS 是 Web 的原始设计,htmx 将其发扬光大;③ 与 SPA 的根本区别在于:htmx 让服务器返回 HTML,前端只负责 DOM 更新,而 SPA 让服务器返回 JSON、前端负责渲染——这意味着模板在服务端维护,不需要前端状态管理;④ htmx 引入一个 CDN 链接即可使用,没有构建工具依赖;⑤ 通过 HX-Request: true 头判断 htmx 请求,同一路由可以为全页请求和 htmx 请求返回不同内容;⑥ 适合内容驱动应用(CMS、后台、电商),不适合高度交互应用(在线编辑器、游戏)。