Tsonglew's Blog

Back

当前关键词已经变成 React,结果却显示 HTML。把页面交给 AI,补一句“搜索有问题,修一下”,它可能改请求,也可能改筛选条件。改动能跑起来,我们还得知道它修复了哪一个原因。

这一讲用慢读的故障实验页制造一个可重复的问题,再练习怎样把证据交给 AI。开始前只需要浏览器开发者工具,代码语法讲到能判断修复是否有效为止。请求和浏览器行为已按 Chrome、MDN 与 React 官方文档核对。

带着第一课的事件路径和第四课的状态归属来排查。先在实验室建立复现记录,再检查第三课原型中的加载、空结果与错误恢复。当前原型采用本地筛选,除非你增加异步搜索,不应声称它已经出现了实验室的请求竞态。

静态 HTML和静态 CSS 页面继续作为语义、布局验收基线。实验源码包含在下载包里,本文的诊断模板也整理在提示词文件中。

先把“有时候不对”变成别人能复现的步骤#

打开实验页,选择“故障模式”,点击“一键复现竞态”。页面先发出编号 1 的 HTML 查询,设定延迟 900ms;80ms 后发出编号 2 的 React 查询,设定延迟 200ms。

第二个查询先完成,结果区显示 React 资料;第一个查询稍后完成,又把它换成 HTML 资料。当前关键词依然是 React,结果却来自编号 1。时间线会记录实际经过的时间、请求编号和处理动作。设定的延迟用于制造先后顺序,实际回调时间受浏览器调度影响。

这是本地 setTimeout 模拟,没有真正向后端发出这两个搜索请求。Network 面板里找不到对应的 API 很正常。实验保留了需要研究的先后关系,去掉了登录和网络环境等干扰。

把这次观察写成一个缺陷记录。

最小复现要保留导致故障的条件。删掉慢请求后再宣布“现在正常”,原来的问题就无从验证了。应用很大时,可以让 AI 先把一个输入框、两次异步返回和一处状态写入提取出来,确认小页面仍能复现,再改实现。

从证据决定查哪一层#

真实项目接上 API 后,一次搜索会经过更多环节。下面这张表帮助我们选择下一项检查,每次围绕一个能被证伪的猜测继续。

已观察到的现象先查哪里要留下的证据
点击后没有预期请求Console、事件处理与表单第一条异常、调用栈、按钮是否触发提交
请求失败或返回内容异常NetworkURL、方法、状态码、请求参数、响应与 Timing
响应正确,页面显示了另一批资料状态写入与请求时序当前查询、请求编号、采用响应的时刻
数据正确,元素没出现或被遮住Elements、Computed、LayoutDOM 是否存在、最终 display、尺寸与溢出关系
输入或点击明显卡顿Performance从操作到下一帧的记录、主线程工作与长任务

用 Network 核对真实请求时,先选对那一次请求,再查看 Headers、Payload 和 Response。需要跨刷新保留记录,可以启用 Preserve log;排缓存问题时记录 Disable cache 的开关状态。保存 HAR 或复制请求前,移除 Cookie、令牌和私人数据。Chrome 的 Network 指南 ↗说明了这些检查入口。

状态正确而样式异常时,选中真正出问题的元素。Styles 中被划掉的声明说明它没有获胜,Computed 给出最终值,Grid 或 Flex 的覆盖层能显示布局关系。慢读卡片被长网址撑宽,就检查容器的最小宽度与文字换行规则。给整个页面加 overflow-x: hidden 会让部分内容消失,应继续查到具体元素。

截图能说明页面长什么样。请求记录和状态时间线才能帮助我们解释它为什么会变成这样。

修复竞态,要约束谁还能更新页面#

切换到“修复模式”,再执行一次相同步骤。第二个查询先完成并采用,第一个查询完成时被丢弃。修复依据是请求编号,只有当前最新请求可以提交结果。

实验中的判断可以概括为下面这个条件。

// requestId 是这次请求创建时保存的编号
// latestRequestId 会在新查询发出时递增
if (requestId !== latestRequestId) {
  return
}
js

这个检查要放在更新可见状态之前。生产代码还要检查错误和 loading 的更新。旧请求失败时不该覆盖新请求的成功页面,旧请求结束时也不能提前关掉新请求的加载提示。

真实 fetch 可以配合 AbortController 取消已经无用的请求。组件卸载或查询变化后,也要阻止过期结果提交。取消请求能减少无用工作,提交前的有效性检查负责保证状态归属。React 官方的 Effect 示例用清理函数忽略过期响应,说明了相同的问题。React 的请求竞态示例 ↗

把输入做防抖,只会减少发出的请求数。已经重叠的两次请求仍然可能倒序完成。验收时必须故意让旧请求晚到,才能看出修复是否覆盖了这个条件。

HTTP 成功以后,数据还要过一道检查#

fetch 收到 404、401 或 500 响应时,不会仅仅因为这些状态码就进入 catch。先检查 response.ok 或具体状态,再解析响应;JSON 能解析也不代表结构正确。MDN 的 Fetch 使用说明 ↗

慢读现有数据的顶层字段是 items 和 total,每条资料包含 id、title、url、description、tags、status。把接口返回值断言成 TypeScript 类型,不会替运行中的程序校验这些字段。

让 AI 在 API 边界按约定的 schema 解析响应。至少检查 items 是数组、必填文本字段为字符串,标签是字符串数组,状态属于 unread、reading、archived,total 是非负整数。资料链接还应限制为业务允许的协议。检查失败要产生可诊断的错误,页面显示“数据暂时无法读取”,不能把异常伪装成空列表。

这里有一个后端同学容易认出的约束。未来接口支持分页后,total 可以大于当前 items.length。校验应遵循接口契约,不要根据眼前只有三条资料就写死相等关系。

空列表也需要独立处理。合法响应里 items 为空,页面应告诉用户没有匹配资料,并允许清除关键词;接口断网、格式损坏和无结果是不同状态。

鉴权、跨域和重复提交,要修在负责它的地方#

慢读将来接入账户和收藏接口时,错误响应不能统一变成“请重试”。401 表示请求缺少有效认证凭据 ↗,通常需要重新认证;403 表示服务器拒绝执行请求 ↗,重新登录未必能解决权限问题。前端显示什么按钮,由产品流程和后端契约共同决定。

CORS 错误则要核对请求的来源、预检结果、服务端允许的来源与凭据设置。不能让 AI 关闭浏览器安全策略,也不能把跨域 API 请求改成 no-cors 来“消除红字”。后者会得到 JavaScript 读不到内容的不透明响应。修复通常在服务端 CORS 配置或明确设计的同源代理上,开发代理也需要对应的生产部署方案。MDN 的 CORS 说明 ↗

收藏按钮连续点两次,是另一类职责分工。前端应在提交期间给出反馈、阻止重复操作,失败后恢复可重试状态。按钮禁用不能处理网络重试、多个标签页或请求已经到达服务器的情况。

如果产品要求一次收藏操作最多生效一次,后端需要落实幂等规则,例如用业务唯一约束,或按用户与幂等键保存一次操作的结果。同一次逻辑操作的重试沿用同一个键,新操作再换键;相同键配了不同载荷应按契约拒绝。前端与后端都要留下可关联的请求标识。POST 方法本身不保证幂等 ↗。

把下面这段诊断请求交给 AI#

先填证据,再发提示词。没有的材料写“尚未取得”,让 AI 提供采集方法。

这段提示词要求 AI 给出因果证据。“构建通过”只能证明一部分工程约束成立,搜索顺序是否正确仍要执行两次异步返回的实验。

用矩阵验收,别只点一次成功路径#

下表分开了当前示例可以直接执行的检查,以及接上真实服务后必须补齐的检查。不要把未来场景写成当前实验已经通过。

场景操作或输入验收结果当前检查位置
旧查询晚到一键复现竞态最终采用 React,HTML 标为丢弃实验页修复模式
键盘操作Tab 移动、Enter 提交、切换单选项顺序合理,焦点可见,操作有结果静态基线与实验页
窄屏和长内容320px 视口,替换长标题、网址、多个标签内容可读,控制项不遮挡,无整页横向溢出静态 CSS 基线
表单为空不填关键词便提交关联字段的错误提示可见,焦点可定位静态表单基线
加载、空结果、断网延迟响应、合法空数组、拒绝请求三种状态可区分,用户能恢复操作接 API 后补测
旧请求失败新请求成功后旧请求报错成功结果保留,不出现过期错误接 API 后补测
错误数据与鉴权字段类型错误、401、403按契约处理,无静默空列表接 API 后补测
重复提交双击、同一操作重试UI 反馈明确,服务器无重复副作用接收藏 API 后补测

桌面设备模式能快速检查窄屏,最后仍要用真实手机看字体、触控和软键盘。把矩阵中的每项标为通过、失败或未执行,附上必要证据。下一次让 AI 改界面时,直接把这份记录交给它,要求重跑受影响的场景。

将故障模式与修复模式的日志分别填入工作簿的第五课,并记录一个你在自己原型中实际检查的场景。功能结果稳定后,下一课再比较同一操作的性能。

后端转全平台 05|AI 写的页面出错了,怎样定位和验收
https://tsonglew.github.io/blog/backend-to-platform-03-css
Author Tsong Lew
Published at 2026年9月23日