渲染分页数据是一种非常常见的 UI 模式,在 TanStack Query 中,它通过在查询键中包含页面信息来“正常工作”:
const result = useQuery(() => {
queryKey: ['projects', page],
queryFn: fetchProjects,
})
const result = useQuery(() => {
queryKey: ['projects', page],
queryFn: fetchProjects,
})
但是,如果您运行这个简单的示例,您可能会注意到一些奇怪的事情:
UI 在 success 和 pending 状态之间来回跳转,因为每个新页面都被视为一个全新的查询。
这种体验并不理想,不幸的是,如今许多工具都坚持这种工作方式。但 TanStack Query 不会!正如您可能已经猜到的那样,TanStack Query 带有一个很棒的功能,称为 placeholderData,它使我们能够解决这个问题。
考虑以下示例,理想情况下,我们希望为查询增加页面索引(或游标)。如果我们使用 useQuery,它在技术上仍然可以正常工作,但是当为每个页面或游标创建和销毁不同的查询时,UI 会在 success 和 pending 状态之间来回跳转。通过将 placeholderData 设置为 (previousData) => previousData 或从 TanStack Query 导出的 keepPreviousData 函数,我们可以获得一些新东西:
import { keepPreviousData, useQuery } from '@tanstack/solid-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>
)
}
import { keepPreviousData, useQuery } from '@tanstack/solid-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 选项也与 useInfiniteQuery 钩子完美配合,因此您可以无缝地允许用户在无限查询键随时间更改时继续查看缓存数据。