使用Django和HTMX简化动态Web开发(第四部分):Payload传输方式
在使用传统JavaScript的fetch进行Ajax请求时,发送POST请求通常需要通过JSON.stringify()手动组装并发送数据负载(payload)。
大致感觉如下:
fetch("/api/todos/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrftoken
},
body: JSON.stringify({
title: "장보기",
done: false,
priority: 3
})
})
那么,在HTMX中情况又是如何呢?hx-post究竟是以何种方式向服务器发送数据的?由于许多示例只展示了非常简单的功能,要找到创建和发送复杂数据负载的实际案例却出乎意料地困难。
本文将彻底梳理HTMX中的数据传输方式以及服务器如何处理这些数据。

HTMX的数据传输方式
fetch的数据负载处理方式与hx-post的数据传输方式存在显著差异。
在fetch中,开发者需要手动创建对象并将其转换为JSON格式放入请求体(body)。而HTMX则默认收集DOM中的值并进行传输。
HTMX的思维方式更接近于“从HTML元素中收集值来构建请求参数”,而非“直接组装JS对象”。通常,它采用以下三种方式:
1) 基于表单收集并发送值
这是最符合HTML规范且与Django配合最佳的方式。
<form hx-post="/todos/create/" hx-target="#todo-list">
<input type="text" name="title" placeholder="标题">
<input type="number" name="priority" value="3">
<input type="hidden" name="done" value="false">
<button type="submit">注册</button>
</form>
在这种情况下,HTMX会收集表单内的所有输入值,并将其封装到请求中发送。默认的编码方式与普通表单提交相同,即URL-encoded form data。
在Django视图中,您可以像往常一样接收这些数据:
def create_todo(request):
title = request.POST.get("title")
priority = request.POST.get("priority")
done = request.POST.get("done")
2) 不使用表单,包含其他元素的值:hx-include
当您希望在一个按钮上使用hx-post,并同时选择性地发送一些分散的输入值时,可以使用此方式。
<input type="text" id="title" name="title" placeholder="标题">
<input type="number" id="priority" name="priority" value="3">
<button hx-post="/todos/create/"
hx-include="#title, #priority"
hx-target="#todo-list">
注册
</button>
hx-include会将指定元素的值包含在请求中。它无需将所有输入项都包裹在<form>标签内,就能产生类似于数据负载的效果,对于少量输入项的场景非常实用。
3) 添加隐藏值或计算值:hx-vals
这是与fetch中直接构建部分数据负载对象概念最接近的功能。
<button hx-post="/todos/create/"
hx-vals='{"title": "장보기", "done": false, "priority": 3}'
hx-target="#todo-list">
快速注册
</button>
hx-vals用于向请求中添加额外的参数。默认情况下,它使用类似JSON的语法,但如果加上js:前缀,也可以发送动态的JavaScript计算值。
<input type="text" id="title" placeholder="标题">
<button hx-post="/todos/create/"
hx-vals='js:{title: document.querySelector("#title").value, done: false, priority: 3}'
hx-target="#todo-list">
注册
</button>
在这种情况下,Django仍然会通过request.POST来读取数据。因为hx-vals只是“使用类似JSON的语法来定义值”,它并不会将请求主体本身转换为JSON格式。
注意:hx-vals与“JSON Payload”不同
这是初次接触HTMX时最容易混淆的地方。
<button hx-post="/my-url/" hx-vals='{"a":1, "b":2}'>
这段代码并非意味着“将JSON对象作为请求体发送”,它更接近于“在请求参数中添加a=1&b=2”。换句话说,服务器仍然需要像处理表单数据一样来处理它。
如果确实需要发送JSON请求体怎么办?
如果您确实需要使用fetch(... JSON.stringify(payload))那样的application/json格式,那么您需要HTMX的json-enc扩展。
按照官方文档的指导,进行如下设置:
<script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/json-enc.js"></script>
<button hx-post="/api/todos/"
hx-ext="json-enc"
hx-vals='{"title": "장보기", "done": false, "priority": 3}'
hx-target="#result">
以JSON发送
</button>
至此,我们才真正实现了熟悉的JSON数据负载方式。此时,Django视图中的request.POST将是空的,您需要直接读取request.body。
import json
def create_todo_api(request):
data = json.loads(request.body)
title = data.get("title")
# ... 处理逻辑 ...
return JsonResponse({"ok": True})
HTMX与DRF哲学理念存在差异,但也无需强行结合。
虽然可以使用扩展来发送JSON,但这不禁让人产生疑问:这真的是HTMX的精髓所在吗?因为这正是“以数据为中心(DRF)”和“以超媒体为中心(HTMX)”这两种哲学理念发生冲突的地方。
如果决定充分利用HTMX,我个人认为或许需要暂时摆脱以数据为中心的思维模式。通过<form>、hx-include、hx-vals发送值,服务器通过request.POST接收,并返回HTML片段而非JSON——这正是HTMX最能发挥其优势的地方。
但如果不想放弃DRF序列化器的便利性怎么办?
DRF的序列化器确实非常强大。难道仅仅因为使用了HTMX,就必须放弃这个便捷的验证工具,转而进行request.POST.get()的繁琐操作吗?
幸运的是,DRF序列化器不仅能出色地验证JSON,也能很好地验证表单数据。由于这部分内容较长,我们将在下一篇文章中深入探讨“HTMX与DRF序列化器的共存之道”。
总结
今天的文章内容总结如下:
- HTMX默认从DOM中收集值。这与
fetch直接构建数据负载对象的方式有所不同。 - 三种传输方式:用于发送整个表单的
<form>,用于选择性发送的hx-include,以及用于添加额外值的hx-vals。 - 默认是表单数据。请注意,
hx-vals使用JSON语法并不意味着它会发送实际的JSON请求体。 - 如果确实需要JSON,请使用
json-enc扩展。但最好记住,HTMX的真正精髓在于交换HTML片段。
下一次,我们将探讨如何将DRF的便利性融入HTMX中!
相关文章阅读