分页查询 / 滞后查询
渲染分页数据是一种非常常见的 UI 模式,在 TanStack Query 中,只需在查询键(query key)中包含页面信息,该模式便“开箱即用”:
TypeScript
1const result = useQuery({2 queryKey: ['projects', page],3 queryFn: () => fetchProjects(page),4})然而,如果你运行这个简单示例,可能会注意到某种异常现象:
UI 会在 success 和 pending 状态之间反复跳变,因为每个新页面都被视为一个全新的查询。
这种体验并不理想,不幸的是,当前许多工具仍坚持如此工作。但 TanStack Query 并非如此!你可能已经猜到,TanStack Query 提供了一个强大的功能——placeholderData,可助你规避此问题。
使用 placeholderData 实现更优的分页查询
考虑如下场景:我们理想情况下希望为某个查询递增 pageIndex(或 cursor)。若仅使用 useQuery,技术上它依然可以正常工作,但每当为不同页面或游标创建并销毁查询时,UI 仍会在 success 和 pending 状态间跳变。而将 placeholderData 设为 TanStack Query 导出的 (previousData) => previousData 或 keepPreviousData 函数后,你将获得以下几项新能力:
- 即使查询键(query key)已变更,上一次成功拉取(fetch)的数据仍可用,直至新数据请求完成。
- 当新数据到达时,先前的
data将被无缝替换为新数据。 isPlaceholderData可用于获知当前查询实际提供给你的数据是哪一份。
TypeScript
1import { keepPreviousData, useQuery } from '@tanstack/ppreact-query'2import { useState } from 'preact/hooks'3 4function Todos() {5 const [page, setPage] = useState(0)6 7 const fetchProjects = (page = 0) =>8 fetch('/api/projects?page=' + page).then((res) => res.json())9 10 const { isPending, isError, error, data, isFetching, isPlaceholderData } =11 useQuery({12 queryKey: ['projects', page],13 queryFn: () => fetchProjects(page),14 placeholderData: keepPreviousData,15 })16 17 return (18 <div>19 {isPending ? (20 <div>Loading...</div>21 ) : isError ? (22 <div>Error: {error.message}</div>23 ) : (24 <div>25 {data.projects.map((project) => (26 <p key={project.id}>{project.name}</p>27 ))}28 </div>29 )}30 <span>Current Page: {page + 1}</span>31 <button32 onClick={() => setPage((old) => Math.max(old - 1, 0))}33 disabled={page === 0}34 >35 Previous Page36 </button>37 <button38 onClick={() => {39 if (!isPlaceholderData && data.hasMore) {40 setPage((old) => old + 1)41 }42 }}43 // Disable the Next Page button until we know a next page is available44 disabled={isPlaceholderData || !data?.hasMore}45 >46 Next Page47 </button>48 {isFetching ? <span> Loading...</span> : null}49 </div>50 )51}使用 placeholderData 使无限查询(infinite query)结果滞后
尽管使用频率较低,placeholderData 选项同样能完美适配 useInfiniteQuery Hook,因此你可以让用户在无限查询键(infinite query keys)随时间变化的过程中,持续无缝地看到缓存数据。