Codex 改分页时,我不会先盯分页组件:页码和数据错位通常断在这条链上
分页出现错位时,最显眼的是页码,所以第一反应往往是检查 current 绑定。
这一步没错,只是太靠近结果。一个页码从用户点击到表格完成渲染,中间至少经过分页事件、列表状态、参数组装、接口分页规则、响应解析和状态提交。前面任何一处把“第几页”解释错了,最后都会表现成分页器不对。
上一篇已经给查询、翻页、重置和删除刷新定过状态规则,这里不再重复“什么操作应该回第一页”。这一篇只回答一个更窄的问题:规则明明写了,页码和数据为什么仍会错位?
先别改 current,把一页数据走过的路画出来
我会让 Codex 先交付一条真实调用链,而不是直接给修复代码:
分页事件 → 页面或分页 Hook 接收 current / size → 列表入口更新分页状态 → 组装最终请求参数 → 接口解释页码并返回 records / total → 前端判断这份响应能否提交 → 更新 list、current、size、total → 分页器与表格重新渲染
这条链上的字段看起来都叫页码,职责却不一样。点击事件里的 current 是用户意图,请求里的 current 是接口契约,页面状态里的 current 是当前视图事实。把三者当成同一个值直接来回覆盖,问题就很难定位。
第一处断点:前后端对“第一页”的编号不同
有的接口从 1 开始计页,有的接口把第一页记为 0。分页组件显示第 1 页,不代表请求一定应该传 1。
如果接口按 0 起算,而页面直接把组件页码传过去,用户点第 2 页时,接口可能收到 2,实际返回第三段数据。反过来,在多个位置都做 current - 1,也可能产生重复转换。
我会把转换限制在接口适配边界:
const toApiPage = ({ current, size }) => ({
pageIndex: current - 1,
pageSize: size
});
这只是结构示意,不是通用接口规范。真正要确认的是接口文档、现有 API 封装和相邻页面的请求。组件内部、页面事件和请求封装不能各自猜一次。
字段名也属于同一类契约问题。页面使用 current、size,接口可能接收 pageNum、pageSize,也可能要求分页对象放进请求体。如果 Codex 只在页面上改变量名,却没有追到 API 封装,界面状态会变化,实际请求却仍沿用旧字段。
第二处断点:同一份页码被两个地方持有
分页错位还有一种常见结构:分页组件内部有一份页码,列表页面或 tabMixin 里又有一份。
用户点击以后,内部页码先变成 3,外部请求失败仍停在 2;或者外部被重置成 1,内部组件没有同步。此时分页器和表格各自都在诚实展示自己的状态,只是两份状态已经分叉。
我会要求明确唯一事实来源:
-
分页组件负责发出用户意图,不私自保存业务页码;
-
列表状态持有已确认的
current、size和total; -
请求失败时,是否提交目标页码要按项目交互约定处理;
-
任何镜像状态都要说明同步方向,不能双向随意赋值。
当前 web-skills 的分页 Hook 将 current 或 size 传回统一 getList,tabMixin 再维护 listData.pageObj。这给出了一个清晰入口。不过,目标项目是否完全采用这份实现,仍要读实际文件,不能因为 Skill 里有示例就跳过代码取证。
第三处断点:请求发出去的值,不是页面以为的值
响应式对象很方便,也会把参数构造时机藏起来。
下面这类写法如果把同一个对象继续交给异步流程或拦截器,调试时看到的值未必等于请求发出瞬间的值:
const params = pageState; listApi(params);
更容易核对的方式,是在请求边界生成一次快照:
const params = {
current: pageState.current,
size: pageState.size,
...fixedParams,
...toApiQuery(committedQuery)
};
return listApi(params);
我关心的不是对象展开这种写法本身,而是能否回答:这次请求最终带了哪个页码、哪些固定条件和哪一版已提交查询。只有页面状态,没有序列化后的请求参数,还不足以证明分页链路正确。
web-skills 当前的 tabMixin 会把 pageObj、otherParams 和已保存查询组合后交给 listApi。审查时要继续看合并顺序,因为同名字段可能被后展开的对象覆盖。比如固定参数里意外带着 current,分页组件传来的新页码就会在请求前被悄悄改回去。
第四处断点:接口校正了页码,前端却没有接住
总数变化后,请求页码可能超出有效范围。部分后端会返回空数组,部分会把页码校正到最后一页,还有的会在响应里返回实际 current 和 size。
如果接口已经把第 5 页校正为第 4 页,前端只读取 records 和 total,却继续保留本地 current = 5,页面就会出现“第 5 页显示第 4 页数据”的错位。
当前 web-skills 的 tabMixin 示例在分页响应中更新 records 和 total,没有直接采用响应里的 current。这并不自动构成错误:若本项目约定前端页码始终有效,或者接口不会校正页码,本地状态就是权威值。只有接口契约明确返回了实际页码,才需要决定是否同步。
因此,我不会让 Codex 看见 res.data.current 就立即补一行赋值。先查清楚三件事:
-
接口是否真的会修正越界页码;
-
响应页码与请求页码使用相同起点吗;
-
前端接受校正后,分页器、序号列和后续刷新是否一起更新。
第五处断点:晚返回的旧响应覆盖了新页面
用户快速点击第 2 页和第 3 页,请求 A、B 依次发出。B 先返回,表格已经显示第 3 页;随后 A 返回,又把列表覆盖成第 2 页数据。如果页码状态仍是 3,错位就出现了。
这是响应提交问题,不是分页事件问题。继续在点击回调里设置 current,修不好它。
常用办法是取消旧请求,或只允许最新请求提交:
let latestRequestId = 0;
const requestList = async (params) => {
const requestId = ++latestRequestId;
const res = await listApi(params);
if (requestId !== latestRequestId) return;
listData.list = res.data.records;
listData.pageObj.total = res.data.total;
};
是否采用序号、取消请求或请求库已有能力,要看项目现状。这段示意只强调一点:响应能返回,不等于它仍有资格更新当前页面。
我会怎样给断点排序
分页错位不适合同时改五处。我通常按能最快排除错误解释的顺序检查:
| 检查位置 | 要核对的事实 | 发现差异后的方向 |
|---|---|---|
| 最终请求 | 页码、每页条数、查询条件是否符合接口 | 修参数构造或契约适配 |
| 接口响应 | 返回的是哪一页、多少条、总数多少 | 确认服务端校正与响应字段 |
| 响应提交 | 是否由旧请求覆盖新请求 | 限制过期响应提交 |
| 页面状态 | current、size、total 是否同源 |
收敛状态所有权 |
| 分页渲染 | 组件绑定值是否来自列表状态 | 修绑定或组件契约 |
这张表故意先看网络事实,再看 UI。因为“分页器显示错了”只能说明结果不一致,不能证明根因就在分页器。
交给 Codex 的排查任务应该写到这里
这张列表出现页码与数据错位。先定位,不要直接修改: - 追踪分页点击到列表渲染的完整调用链; - 标出组件页码、列表页码、请求页码和响应页码各自的起点与所有者; - 给出一次请求的最终参数,而不是只读响应式对象; - 核对响应是否返回或校正 current、size、total; - 检查多个请求返回顺序以及响应提交条件; - 每个判断附文件位置或运行证据,并指出仍未确认的接口约定。 找到第一个断点后,只在对应边界给最小修正方案。不要在多个事件里重复设置 current,也不要绕开项目已有的 tabMixin、paging 和请求封装。
如果缺少可运行环境,Codex 可以完成静态调用链和风险判断,但不能把接口行为写成已验证。页码起点、后端校正和并发返回顺序,最终都需要网络证据。
写在最后
分页器只是链路末端的一块显示。页码与数据错位,可能来自编号契约、参数覆盖、双份状态、服务端校正,也可能只是旧响应晚到了。
把这些位置画成一条链,修复才有明确落点。否则在查询、翻页和重置事件里反复补 current = 1,表面上挡住一个路径,下一次连续操作还会复发。
下一篇继续做排查,但不再扩展根因。我会把请求前、响应后和状态提交时的证据整理成一份最小记录,让 Codex 能根据同一份现场判断断点,而不是靠日志数量猜问题。
本系列持续更新。分页链路收束后,将进入 Loading 的页面、按钮和局部状态边界。
参考资料
更多推荐



所有评论(0)