Flask 与 JavaScript使用 fetch 与 JSON 构建动态网页实战指南【免费下载链接】flaskThe Python micro framework for building web applications.项目地址: https://gitcode.com/gh_mirrors/fl/flask导读本文是 Flask 官方文档中 docs/patterns/javascript.rst 的深度技术解析。你将从中学到如何在不刷新页面的前提下用现代浏览器内置的fetchAPI 与 Flask 后端完成数据交互包括如何在模板渲染阶段安全地把数据注入 JavaScript、如何在 JS 中动态生成后端 URL、如何收发 JSON 数据并优雅处理重定向与页面内容替换。文末结合仓库源码与 examples/javascript 示例工程带你写出可直接运行的前后端联动代码。为什么还需要JavaScript AJAX模式传统 HTMLform提交会触发整页刷新与重定向而现代 Web 应用追求的是局部数据更新。Flask 官方文档给出的思路是在页面中加入 JavaScript用fetch向服务器发起异步请求再原地替换页面内容。这里需要澄清一个常见误区fetch并不是什么新框架它是浏览器内置的标准 API是 MDN Fetch API 就用同一套视图同时演示了fetch、XHR 与 jQuery 三种写法方便对照app.route(/, defaults{js: fetch}) app.route(/any(xhr, jquery, fetch):js) def index(js): return render_template(f{js}.html, jsjs)渲染模板服务端与浏览器的时间差理解 JavaScript 在 Flask 项目中的角色首先要分清两条执行时间线模板在服务器端渲染发生在响应发送给浏览器之前JavaScript 在用户浏览器中运行发生在模板渲染完成并交付之后。因此JavaScript 不可能反向影响 Jinja 模板的渲染结果但反过来——你完全可以把数据渲染进将要运行的 JavaScript 代码中。这是整个模式的第一块基石。用tojson过滤器安全注入数据把 Python 数据传给页面中的script块官方推荐使用 Jinja 的tojson过滤器。它在渲染时把数据序列化为合法的 JavaScript 对象字面量同时转义不安全字符如/script防止破坏页面结构或引入 XSS 风险。如果你忘了用tojson浏览器控制台通常会报SyntaxError。data generate_report() return render_template(report.html, chart_datadata)script const chart_data {{ chart_data|tojson }} chartLib.makeChart(chart_data) /script仓库的测试 tests/test_json.py 专门验证了这个行为——注入包含/script和datetime的数据后渲染结果中/script被转义为\u003c/script\u003edatetime被序列化为 RFC 822 格式的 HTTP 日期字符串rv flask.render_template_string( const data {{ data|tojson }};, data{name: /script, time: datetime.datetime(2021, 2, 1, 7, 15)}, ) # 输出 # const data {name: \u003c/script\u003e, time: Mon, 01 Feb 2021 07:15:00 GMT};这段测试也顺带揭示了 Flask 序列化的底层能力源码 src/flask/json/provider.py 中的_default函数支持把datetime/date转为 HTTP 日期字符串、UUID转字符串、dataclass自动转 dict这正是tojson比裸json.dumps更适合注入模板的原因。少见的替代写法把数据放进 HTML 标签的data-属性也是一种可行方案但此时必须使用单引号包裹属性值data-chart{{ chart_data|tojson }}否则会产出非法甚至不安全的 HTML。tojson默认输出双引号字符串恰好适合这种场景。生成 URL两条路径要让 JavaScript 发起请求前提是它知道该请求哪个 URL。官方文档给出了两条互补的路径。路径一模板渲染时直接用url_for最简单的做法是继续用 Flask 的url_for在渲染模板时把结果注入 JavaScriptconst user_url {{ url_for(user, idcurrent_user.id)|tojson }} fetch(user_url).then(...)url_for在 src/flask/helpers.py 中定义它要求处于活跃的请求或应用上下文中最终委托给current_app.url_for()完成路由反解生成的 URL 会正确处理SCRIPT_NAME前缀、静态文件挂载点等细节。路径二运行时在 JS 里拼 URL但有些信息只有浏览器运行时才知道比如用户在页面上点击产生的 id。正如前面所说JS 运行时模板早已渲染完毕此时url_for已不可用。这时你需要知道应用被挂载的根 URLSCRIPT_ROOT——简单部署下它是/但也可能是https://example.com/myapp/这样的前缀路径。官方推荐在渲染模板时把根 URL 存为全局变量之后在 JS 里用模板字符串拼接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(...)这里request.script_root取的是当前请求的脚本根路径tojson会把它安全地转成合法的 JS 字符串字面量。用fetch发起请求从 GET 到 POSTfetch(url, options)接收一个 URL 和一个配置对象返回一个Promise。官方文档刻意保持克制只介绍then()回调链不深入await与其它回调更多细节以 MDN 文档为准。GET读取 JSON默认使用 GET 方法。若响应体是 JSON可以在then()链中逐级解析const room_url {{ url_for(room_detail, idroom.id)|tojson }} fetch(room_url) .then(response response.json()) .then(data { // data is a parsed JSON object })POST 表单数据推荐发送数据时改用 POST并通过body选项携带负载。最常用的数据格式是表单数据FormData与 JSON 两种。表单数据与 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(...)官方示例 examples/javascript/js_example/views.py 演示了服务端接收表单数据的完整形态还展示了request.form.get的类型转换能力app.route(/add, methods[POST]) def add(): a request.form.get(a, 0, typefloat) b request.form.get(b, 0, typefloat) return jsonify(resulta b)对应的前端fetch.html用new FormData(this)直接把整个form序列化后 POST 出去fetch({{ url_for(add)|tojson }}, { method: POST, body: new FormData(this) }) .then(parseJSON) .then(addShow);示例仓库的测试 examples/javascript/tests/test_js_example.py 验证了request.form.get(a, 0, typefloat)的容错性传b这类非法浮点时会回退到默认值0保证接口稳健。POST JSON 数据注意 Content-Type优先选择表单数据因为它与 HTML 表单语义一致、更简单。只有当数据结构确实复杂嵌套对象、数组时才需要 JSON。发送 JSON 时必须同时携带Content-Type: application/json请求头——否则 Flask 会返回415 Unsupported Media Type错误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(...)跟随重定向别让登录响应悄悄丢掉fetch会自动跟随HTTP 重定向比如 302/303但浏览器不会因此改变页面——你的视图若在 JS 登录后返回了一个 redirect 响应页面会停在原地。此时需要手动检查响应的redirected标志并把response.url赋给window.location完成跳转fetch(/login, {body: ...}).then( response { if (response.redirected) { window.location response.url } else { showLoginError() } } )替换页面内容局部更新的终点响应也可能是新 HTML——可以是一段新区域也可以是整张新页面。若返回的是整页官方建议用上面的重定向方案处理而不是用 JS 把整页塞进 DOM。下面的示例展示如何用请求返回的 HTML 替换页面里的divdiv idgeology-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示例工程 fetch.html 的addShow函数是同一思路的简化版拿到 JSON 后只更新#result这个span的文本实现计算器不刷新页面出结果的体验function addShow(data) { var span document.getElementById(result); span.innerText data.result; }在视图里返回 JSON直接返回 dict自动序列化Flask 视图可以直接返回 Python 字典框架会自动把它序列化为 JSON 响应mimetype 为application/json。结合url_for还能在 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, filenamefusers/{id}/profile.png), }从源码看自动序列化与jsonify最终都汇聚到 src/flask/json/provider.py 的JSONProvider.response并由 DefaultJSONProvider.response 落地在debug 模式下输出会带 2 空格缩进便于阅读非 debug 模式则使用紧凑分隔符(, , :)压缩体积末尾追加一个换行符。用jsonify返回任意 JSON 类型如果返回的不是字典比如列表就需要jsonify它会创建带application/jsonmimetype 的响应对象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])jsonify的实现位于 src/flask/json/init.py要么传位置参数单值或列表要么传关键字参数作为 dict 序列化两者不可混用。dumps/loads/jsonify这一族函数都优先走current_app.json的 provider这也意味着你可以通过自定义JSONProvider子类替换整套 JSON 序列化行为比如换成 orjson、更改ensure_ascii、sort_keys、compact等配置见 src/flask/json/provider.py。为什么不要把文件数据塞进 JSON不要在 JSON 响应里直接返回文件内容JSON 无法原生表达二进制数据必须 base64 编码这既拖慢速度、增加带宽也不利于缓存。正确做法是用一个视图专门提供文件下载把文件的 URL 放进 JSON让客户端拿到 JSON 后再发起独立请求获取资源与上文JSON 中返回图片 URL的模式一脉相承。在视图里接收 JSON接收请求体中的 JSON直接使用flask.request.json属性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()这里有两个值得记住的错误语义它们是前后端联调时最常见的报错来源请求体不是合法 JSON → 抛出400 Bad RequestContent-Type头不是application/json→ 抛出415 Unsupported Media Type与发送 JSON 时必须带 Content-Type 头的约定正好呼应。结合示例工程跑通完整闭环仓库 examples/javascript 提供了一个可直接运行的完整示例flask --app js_example run后访问 http://127.0.0.1:5000 即可体验三种前端实现其前后端链路正好覆盖本文全部知识点后端 views.py 提供GET /渲染页面与POST /add接收表单、返回 JSON两个端点前端 base.html 定义#calc表单与#result结果区fetch.html、xhr.html、jquery.html 分别用三种方式实现提交 → 局部更新其中 XHR 版本展示了被fetch取代的旧式写法var request new XMLHttpRequest(); request.addEventListener(load, addShow); request.open(POST, {{ url_for(add)|tojson }}); request.send(new FormData(this));测试 test_js_example.py 用 Flask 测试客户端验证了页面渲染模板与/add的数值计算逻辑。核心要点速查场景关键做法注意事项模板 → JS 注入数据{{ data|tojson }}不转义会触发浏览器SyntaxError还可能引入 XSSdata-属性传数据data-x{{ data|tojson }}必须用单引号包裹模板期生成 URL{{ url_for(...)|tojson }}需要活跃的请求上下文JS 运行时拼 URLSCRIPT_ROOT /path/ id根路径来自request.script_root发送表单数据body: new FormData(...)视图用request.form读取最简单推荐发送 JSONbody: JSON.stringify(obj)Content-Type: application/json缺请求头会收到 415返回 JSON视图直接返回 dict或用jsonifydebug 模式自动缩进、非 debug 紧凑输出接收 JSONrequest.json坏 JSON → 400头不对 → 415处理重定向检查response.redirected手动window.location response.urlfetch自动跟随但不改页面文件下载JSON 里放 URL客户端二次请求不要 base64 塞进 JSON掌握这套服务端渲染注入 前端fetch异步交互的组合拳你就能在 Flask 应用中实现登录、搜索、评论、实时刷新等各类无刷新交互——所有能力都来自现代浏览器内置 API无需引入任何第三方 JavaScript 库。【免费下载链接】flaskThe Python micro framework for building web applications.项目地址: https://gitcode.com/gh_mirrors/fl/flask创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考