# 分页查询

## **分页查询 / 延迟查询**

渲染分页数据是一种非常常见的 UI 模式。在 TanStack Query 中，只需将页面信息包含在查询键（query key）中，它就能“正常工作”：

```tsx
const result = useQuery({
  queryKey: ['projects', page],
  queryFn: fetchProjects,
})
```

然而，如果你运行这个简单的示例，可能会注意到一个奇怪的现象：

**UI 会在 `success`（成功）和 `pending`（加载中）状态之间来回跳动，因为每个新页面都被视为一个全新的查询。**

这种体验并不理想，不幸的是，这正是许多工具目前的工作方式。但 TanStack Query 不会如此！正如你可能猜到的，TanStack Query 提供了一个名为 `placeholderData` 的强大功能，可以解决这个问题。

## **使用 `placeholderData` 实现更优的分页查询**

考虑以下示例，我们理想情况下希望为查询递增一个 `pageIndex`（或游标）。如果我们使用 `useQuery`，**技术上它仍然可以正常工作**，但 UI 会随着不同页面或游标创建和销毁不同的查询而在 `success` 和 `pending` 状态之间跳动。通过将 `placeholderData` 设置为 `(previousData) => previousData` 或使用 TanStack Query 导出的 `keepPreviousData` 函数，我们可以获得以下优势：

* **即使查询键已更改，在请求新数据时，上一次成功获取的数据仍然可用**。
* 当新数据到达时，旧的 `data` 会无缝切换为新数据。
* 提供了 `isPlaceholderData` 标志，用于判断当前查询返回的数据是否为占位数据。

```tsx
import { keepPreviousData, useQuery } from '@tanstack/react-query'
import React from 'react'

function Todos() {
  const [page, setPage] = React.useState(0)

  const fetchProjects = (page = 0) =>
    fetch('/api/projects?page=' + page).then((res) => res.json())

  const { isPending, isError, error, data, isFetching, isPlaceholderData } =
    useQuery({
      queryKey: ['projects', page],
      queryFn: () => fetchProjects(page),
      placeholderData: keepPreviousData,
    })

  return (
    <div>
      {isPending ? (
        <div>加载中...</div>
      ) : isError ? (
        <div>错误: {error.message}</div>
      ) : (
        <div>
          {data.projects.map((project) => (
            <p key={project.id}>{project.name}</p>
          ))}
        </div>
      )}
      <span>当前页码: {page + 1}</span>
      <button
        onClick={() => setPage((old) => Math.max(old - 1, 0))}
        disabled={page === 0}
      >
        上一页
      </button>
      <button
        onClick={() => {
          if (!isPlaceholderData && data.hasMore) {
            setPage((old) => old + 1)
          }
        }}
        // 在确认有下一页之前禁用“下一页”按钮
        disabled={isPlaceholderData || !data?.hasMore}
      >
        下一页
      </button>
      {isFetching ? <span> 加载中...</span> : null}
    </div>
  )
}
```

## **使用 `placeholderData` 延迟无限查询结果**

虽然这种情况不太常见，但 `placeholderData` 选项与 `useInfiniteQuery` Hook 也能完美配合，让你的用户在无限查询的键随时间变化时，依然能够无缝地看到缓存的数据。
