---
id: infinite-queries
track: learn
product: query
locale: zh-CN
order: 15
revision: 1
sourceCheckedOn: "2026-08-03"
versionRange: "Query ^5"
verifiedAgainst: "@tanstack/react-query@5.101.4"
contentModel: 2
prerequisites:
  - "理解 Query Key 与 queryFn"
  - "后端提供稳定游标或明确页码语义"
sourceRefs:
  - "https://tanstack.com/query/latest/docs/framework/react/guides/infinite-queries"
  - "https://tanstack.com/query/latest/docs/framework/react/reference/useInfiniteQuery"
---

# 让无限列表保持有界和可恢复

> 用 initialPageParam、游标和 maxPages 建立明确的翻页契约，同时保持 pages 与 pageParams 的结构一致。

**Outcome:** 能实现不会重复加载、不会无限占用内存并能正确判断末页的“加载更多”列表。

## 页面数据不是普通数组

无限查询缓存同时保存 data.pages 和 data.pageParams。initialPageParam 是必填起点；getNextPageParam 返回下一次 queryFn 收到的 pageParam，返回 null 或 undefined 表示没有下一页。手动修改缓存时必须保留这两个数组及其对应关系。

## 让服务端游标成为翻页事实

游标应来自上一页响应，而不是由客户端猜测。queryKey 描述列表筛选条件，pageParam 只描述这次请求的页位置；不要把不断变化的游标追加进 queryKey，否则每页会变成互不关联的查询。

File: `src/features/issues/queries.ts`

```ts
import { infiniteQueryOptions } from '@tanstack/react-query'

export const issuesInfiniteOptions = infiniteQueryOptions({
  queryKey: ['issues', 'infinite'] as const,
  initialPageParam: null as string | null,
  queryFn: async ({ pageParam, signal }) => {
    const search = pageParam ? `?cursor=${encodeURIComponent(pageParam)}` : ''
    const response = await fetch(`/api/issues${search}`, { signal })
    if (!response.ok) throw new Error('Unable to load issues')
    return response.json() as Promise<{ items: Issue[]; nextCursor: string | null }>
  },
  getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
  maxPages: 5,
})
```

## 分开首屏、下一页与后台更新

按钮应同时检查 hasNextPage 与 isFetchingNextPage，防止同一次用户动作重复请求。界面要区分首次 pending、加载下一页以及后台 refetch；maxPages 可以限制内存与顺序重取成本，双向列表还必须同时定义 getPreviousPageParam。

## 实践与验证

### 完成检查点

每次加载使用上一页返回的游标，末页后 hasNextPage 为 false，缓存中的 pages 与 pageParams 长度始终相同。

### 动手练习

为 Issue 时间线实现最多保留五页的“加载更多”，并在切换筛选条件后从独立缓存的第一页开始。

### 验证方法

记录前三次请求的游标并模拟末页、失败重试与快速双击；游标不重复、失败页可重试、同一时刻不出现重复下一页请求。

### 常见错误

- 把 pageParam 放进 queryKey，让每一页成为独立 Query。
- 手动改 pages 却漏掉 pageParams，破坏 InfiniteData 结构。

## Official sources

- https://tanstack.com/query/latest/docs/framework/react/guides/infinite-queries
- https://tanstack.com/query/latest/docs/framework/react/reference/useInfiniteQuery
