fetch
和 JSON¶
你可以想要在数据改变时动态改变局部 HTML 页面,而不是重载整个页面。
如果要实现这个功能,那么就不应当提交一个 HTML <form>
,直接执行重
定向来重新渲染模板。你可以添加 JavaScript ,调用 fetch()
,替换页
面上的内容。
fetch()
是现代的、内置的 JavaScript 解决方案,用于从页面上进行请求。
你可能听说过其他的“ AJAX ”方法和库,如 XMLHttpRequest()
或 jQuery 。这些在
现代浏览器中已经不再需要了。当然你可以根据应用需求选择使用它们或其他
库。本文将只关注内置的 JavaScript 功能。
渲染模板¶
了解模板和 JavaScript 之间的区别是很重要的。模板是在响应被发送到用户 浏览器之前,在服务器上渲染的。 JavaScript 在用户的浏览器中运行的,在 模板被渲染和发送之后。因此,我们不可能使用 JavaScript 来影响如何渲染 Jinja 模板,但有可能将数据渲染到将要运行的 JavaScript 中。
为了在渲染模板时向 JavaScript 提供数据,可以在 <script>
块中使用
tojson()
过滤器。这将使数据转换为有效的
JavaScript 对象,并确保任何不安全的 HTML 字符被安全渲染。如果你不使用
tojson
过滤器,你会在浏览器控制台中得到一个 SyntaxError
。
data = generate_report()
return render_template("report.html", chart_data=data)
<script>
const chart_data = {{ chart_data|tojson }}
chartLib.makeChart(chart_data)
</script>
一个不太常见的模式是将数据添加到一个 HTML 标签的 data-
属性中。在
这种情况下,你必须在值周围使用单引号,而不是双引号,否则就会产生无效
的或不安全的 HTML 。
<div data-chart='{{ chart_data|tojson }}'></div>
生成 URL¶
从服务器上获取数据到 JavaScript 的另一种方法是为数据提出一个 请求。首先,你需要知道要请求的URL。
生成 URL 的最简单方法是当渲染模板时,继续使用 url_for()
。
例如:
const user_url = {{ url_for("user", id=current_user.id)|tojson }}
fetch(user_url).then(...)
然而,你可能只能根据 JavaScript 中的信息来生成 URL 。如上所述,
JavaScript 是在用户的浏览器中运行的,而不是模板的一部分,所以不能使用
url_for
。
在这种情况下,你需要知道“ root URL ”,即你的应用是在哪个 URL 下运行
的。在简单的设置中,一般是 /
,但也可能是其他的,比如
https://example.com/myapp/
。
如何告诉 JavaScript 代码 root URL ?一个简单方法是在渲染时将其设置为 全局变量,然后在从 JavaScript 生成 URL 时使用它。
const SCRIPT_ROOT = {{ request.script_root|tojson }}
let user_id = ... // do something to get a user id from the page
let user_url = `${SCRIPT_ROOT}/user/${user_id}`
fetch(user_url).then(...)
使用 fetch
提出请求¶
fetch()
接收两个参数,一个 URL 和一个带有其他选项的对象,它返回一个
Promise
。我们不会涉及所有可用的选项,将只在 promise 上使用
then()
,而不是其他回调或 await
语法。阅读链接的 MDN 文档,了
解更多信息。
默认情况下,使用 GET 方法。如果响应包含 JSON ,那么可以被用于
then()
回调链。
const room_url = {{ url_for("room_detail", id=room.id)|tojson }}
fetch(room_url)
.then(response => response.json())
.then(data => {
// data is a parsed JSON object
})
要发送数据,请使用一个数据方法,如 POST ,并传递 body
选项。最常
见的数据类型是表单数据或 JSON 数据。
要发送表单数据,请传递一个已填充的 FormData
对象。这个对象使用使用
与 HTML 表单相同的格式,可以在 Flask 视图中用 request.form
访问。
let data = new FormData()
data.append("name": "Flask Room")
data.append("description": "Talk about Flask here.")
fetch(room_url, {
"method": "POST",
"body": data,
}).then(...)
一般来说,最好将请求数据作为表单数据发送,就像提交 HTML 表单时那样。
JSON 可以表示更复杂的数据,但除非必要,否则最好坚持使用更简单的格式。
当发送 JSON 数据时,必须同时发送 Content-Type: application/json
头,否则 Flask 会返回一个 400 错误。
let data = {
"name": "Flask Room",
"description": "Talk about Flask here.",
}
fetch(room_url, {
"method": "POST",
"headers": {"Content-Type": "application/json"},
"body": JSON.stringify(data),
}).then(...)
跟随重定向¶
一个响应可能是一个重定向,例如,如果你用 JavaScript 而不是传统的 HTML 表单登录,并且你的视图返回了一个重定向而不是 JSON 。 JavaScript 请求 是跟随重定向的,但是并不改变页面。如果你想改变页面,那么可以检查响应 并手动重定向。
fetch("/login", {"body": ...}).then(
response => {
if (response.redirected) {
window.location = response.url
} else {
showLoginError()
}
}
)
替换内容¶
响应可能是新的 HTML ,要么是添加或替换页面的一部分,要么是一个全新的
页面。一般来说,如果你要返回整个页面,那么最好用前文所述的重定向来处
理。下面的例子展示如何用请求返回的 HTML 替换 <div>
。
<div id="geology-fact">
{{ include "geology_fact.html" }}
</div>
<script>
const geology_url = {{ url_for("geology_fact")|tojson }}
const geology_div = getElementById("geology-fact")
fetch(geology_url)
.then(response => response.text)
.then(text => geology_div.innerHtml = text)
</script>
从视图返回 JSON¶
如果要从你的 API 视图返回一个 JSON 对象,那么可以从视图直接返回一个字 典,它会自动序列化为 JSON 。
@app.route("/user/<int:id>")
def user_detail(id):
user = User.query.get_or_404(id)
return {
"username": User.username,
"email": User.email,
"picture": url_for("static", filename=f"users/{id}/profile.png"),
}
如果你需要返回其他 JSON 类型,那么可以使用
jsonify()
函数,它会用给定的数据创建一个响应对象,
并序列化为 JSON 。
from flask import jsonify
@app.route("/users")
def user_list():
users = User.query.order_by(User.name).all()
return jsonify([u.to_json() for u in users])
在 JSON 响应中返回文件数据通常不是一个好主意。 JSON 不能直接表示二进制数据,所以它必须经过 base64 编码。 这可能会很慢,占用更多的带宽,而且不容易缓存。
相反,应当使用一个视图来提供文件,并在 JSON 中包含一个指向所需要文件 的 URL 。然后,在获得 JSON 后,客户端提出一个单独请求得到链接的资源。
在视图中接收 JSON¶
使用 request
对象的 json
属性,
将请求的主体解码为JSON。如果主体不是有效的 JSON ,或者
Content-Type
头没有设置为 application/json
,就会出现 400 Bad
Request 错误。
from flask import request
@app.post("/user/<int:id>")
def user_update(id):
user = User.query.get_or_404(id)
user.update_from_json(request.json)
db.session.commit()
return user.to_json()
讨论区