本节摘要:此前页面与后端的交互靠表单加整页跳转,本节换成现代方式:JavaScript 用 Fetch 直接调第 5 章的 API,拿到 JSON 后只更新页面的局部——不闪屏、不跳转。顺带排掉开发期必踩的跨域坑:浏览器的同源策略是什么、CORS 放行怎么配。学完本节,轻记账的页面与数据彻底解耦,真正的前后端分离成形。
阅读完本节,你应当能够:
回想 4.3 的记账流程:提交表单 → 整页刷新 → 重定向回首页。能用,但代价明显:全页重载,网速慢时白屏半天;状态丢失,用户滚动位置、未提交的草稿全部归零;交互僵硬,想做个"输完金额实时显示合计"的动态效果,表单模式根本做不到。Fetch 的思路是让 JavaScript 在后台发请求、拿数据、改局部,页面本身不动——第 4 章学的"JS 三拍"(找元素、绑事件、改内容)在此补全最后一环:改内容的原料来自 API。

// GET:读账目列表 async function loadRecords() { const resp = await fetch("/api/records?month=2026-09"); if (!resp.ok) { // 401、404、500 都会走到这 showMessage("加载失败,状态码 " + resp.status); return; } const records = await resp.json(); // JSON 文本解析成数组 renderList(records); // 改局部:重画列表 } // POST:记一笔 async function addRecord(item, amount) { const resp = await fetch("/api/records", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ item, amount }), }); if (resp.status === 201) { loadRecords(); // 成功后重新拉列表 } else if (resp.status === 400) { const err = await resp.json(); showMessage(err.error); // 用服务器的报错文案 } else { showMessage("网络或服务器异常"); } }
四个要点。async 与 await 是现代写法:网络请求要等,await 让代码读起来像同步,不嵌套回调。状态码要分层看:resp.ok 管"有没有成功",具体状态码管"为什么没成功"——网络断开抛异常走 catch,服务器报错走状态码分支,业务校验错(400)读响应体里的 error 字段。POST 要显式声明内容类型:服务器才知道怎么解析正文。成功后重拉列表:前端不做本地账本,永远以服务器数据为准,避免两份数据对不上。
本地开发时你很可能撞上这样的报错:请求被浏览器的同源策略拦截。同源指协议、域名、端口三者完全一致;三者任一不同就是跨域,浏览器默认禁止页面里的脚本读取跨域响应——这是浏览器在保护用户:防止恶意网站拿着你的登录态(Cookie)偷偷访问你的银行页面。策略挡的是浏览器,curl 不受限制,这就是"接口明明是好的、页面里就是不通"的原因。
放行的方式是 CORS:服务器在响应头里声明"我允许谁跨域访问我"。开发期最省事的做法:
# Flask 侧安装并启用 CORS 支持 # pip install flask-cors 后: from flask_cors import CORS CORS(app) # 全部接口放行所有来源 —— 仅限开发期 # 生产环境应收紧为: # CORS(app, origins=["你的正式域名"], supports_credentials=True)
分寸要拿捏:全放行等于对全世界拆门,生产环境必须收窄到自己的域名。前端开发期还有个更朴素的绕法:让页面和 API 走同一个源——把前端页面也交给 Flask 的静态服务,跨域问题从根上不存在,6.2 的项目结构正是这么安排的。
把 4.4 的模板页改造成 Fetch 版:
<ul id="record-list"></ul> <form id="add-form"> <input id="item" placeholder="用途"> <input id="amount" type="number" step="0.01" placeholder="金额"> <button type="submit">记一笔</button> </form> <script> function renderList(records) { const box = document.getElementById("record-list"); box.innerHTML = records.map(r => `<li>${r.item} — ${r.amount} 元(${r.date})</li>`).join(""); } document.getElementById("add-form").addEventListener("submit", function (e) { e.preventDefault(); // 拦下表单默认行为,交给 Fetch addRecord( document.getElementById("item").value, Number(document.getElementById("amount").value), ); }); loadRecords(); // 页面打开即拉数据 </script>
打开页面,列表从 API 实时渲染;提交一笔,列表无刷新追加。注意模板引擎的角色变化:4.4 里它负责把数据织进 HTML,现在页面是静态壳,数据全部由 Fetch 注入——模板只管骨架,不再参与数据。变式一:给列表加删除按钮,点击后调 DELETE 接口再重拉。变式二:故意停掉后端服务再刷新页面,观察 Fetch 的异常分支怎么走——前端错误处理平时看不见,故障时才显身手。
解耦完成,代码也该收拾收拾了。下一节按工程惯例整理项目结构,让 v1.0 的代码长出正规军的队形。