执行契约
- 适用场景
- 一个列表需要追加加载页面,并由服务端游标或明确 offset 决定下一页。
- 不要使用
- 普通分页需要可分享页码和随机跳页时,优先把 page 放入 URL 并使用常规 Query。
- 前置检查
- 定义 initialPageParam、末页信号、稳定 Query Key、页面响应形状以及是否需要 maxPages 或双向翻页。
- 验证结果
- 记录连续 pageParam,验证末页、失败重试、筛选切换与重复点击;pages 和 pageParams 必须始终一一对应。
- 失败模式
- 重复页通常来自客户端猜测游标、getNextPageParam 返回旧值或并发触发 fetchNextPage;从网络记录和最后一页响应定位。
- 安全边界
- 游标是外部输入且可能被篡改;服务端必须验证作用域、排序与用户权限,不能把游标当作授权令牌。
输入契约
确定第一页 pageParam、响应中的 nextCursor、末页信号以及筛选条件。筛选属于 queryKey,游标属于 pageParam。后端若使用 offset,也要从 lastPageParam 明确推导,不能依赖组件中的局部页码。
最小实现
initialPageParam 与 getNextPageParam 在 v5 中为必需契约。返回 undefined 结束列表;用 AbortSignal 停止过期列表请求。maxPages 大于 0 时,双向翻页还必须提供 getPreviousPageParam。
import { infiniteQueryOptions } from '@tanstack/react-query'
export const issueFeedOptions = infiniteQueryOptions({
queryKey: ['issues', 'feed'] as const,
initialPageParam: null as string | null,
queryFn: ({ pageParam, signal }) => fetchIssuePage(pageParam, signal),
getNextPageParam: (page) => page.nextCursor ?? undefined,
maxPages: 5,
})缓存与交互检查
渲染前 flatten data.pages,但写缓存时保留 `{ pages, pageParams }`。加载按钮由 hasNextPage 与 isFetchingNextPage 共同控制;测试末页、失败重试、筛选切换、快速重复触发以及 maxPages 淘汰方向。