无限查询 infinite-queries
可以将附加数据"加载"更多数据到现有数据集,或者是"无限滚动"上的呈现列表也是一种非常常见的 UI 模式。React Query 支持一个有用的useQuery版本,称为useInfiniteQuery,用于查询这些类型的列表。
使用useInfiniteQuery时,需要注意一些不同之处:
data现在是一个包含无限查询数据的对象data.pages数组包含已获取的分页页面data.pageParams数组包含用于获取分页页面的参数
- 现在可以使用
fetchNextPage和fetchPreviousPage函数 getNextPageParam和getPreviousPageParam选项可用于确定是否有更多数据要加载和获取它所需的信息。 此信息作为查询函数中的附加参数提供(在调用fetchNextPage或fetchPreviousPage函数时可以有选择地覆盖此信息)hasNextPage布尔值现在可用。如果getNextPageParam返回的值不是undefined的,则为truehasPreviousPage布尔值现在可用。如果getPreviousPageParam返回的值不是undefined的,则为trueisFetchingNextPage和isFetchingPreviousPage布尔值来区分后台刷新状态和加载更多状态
注意:在使用
initialData之类的选项或在查询中选择select时,请确保在重组(restructure)数据时仍包含data.pages和data.pageParams属性,否则查询返回时将覆盖您的更改!
例子
假设我们有一个基于游标索引的,且每次返回projects第三页的 API,以及一个可用于获取下一组项目的游标:
1fetch("/api/projects?cursor=0");2// { data: [...], nextCursor: 3}3fetch("/api/projects?cursor=3");4// { data: [...], nextCursor: 6}5fetch("/api/projects?cursor=6");6// { data: [...], nextCursor: 9}7fetch("/api/projects?cursor=9");8// { data: [...] }有了这些信息,我们可以通过以下方式创建"加载更多"的 UI:
- 默认情况下,等待
useInfiniteQuery请求第一组数据 - 返回
getNextPageParam中下一个查询的信息 - 调用
fetchNextPage函数
注意:请勿使用参数调用
fetchNextPage,这很重要。除非您希望它们覆盖从getNextPageParam函数返回的pageParam数据。 例如,不要这样做:<button onClick={fetchNextPage} />,因为这会将onClick事件发送到fetchNextPage函数。
1import { useInfiniteQuery } from "react-query";2 3function Projects() {4 const fetchProjects = ({ pageParam = 0 }) =>5 fetch("/api/projects?cursor=" + pageParam);6 7 const {8 data,9 error,10 fetchNextPage,11 hasNextPage,12 isFetching,13 isFetchingNextPage,14 status,15 } = useInfiniteQuery("projects", fetchProjects, {16 getNextPageParam: (lastPage, pages) => lastPage.nextCursor,17 });18 19 return status === "loading" ? (20 <p>Loading...</p>21 ) : status === "error" ? (22 <p>Error: {error.message}</p>23 ) : (24 <>25 {data.pages.map((group, i) => (26 <React.Fragment key={i}>27 {group.projects.map((project) => (28 <p key={project.id}>{project.name}</p>29 ))}30 </React.Fragment>31 ))}32 <div>33 <button34 onClick={() => fetchNextPage()}35 disabled={!hasNextPage || isFetchingNextPage}36 >37 {isFetchingNextPage38 ? "Loading more..."39 : hasNextPage40 ? "Load More"41 : "Nothing more to load"}42 </button>43 </div>44 <div>{isFetching && !isFetchingNextPage ? "Fetching..." : null}</div>45 </>46 );47}当无限查询需要重新获取时会发生什么?
当无限查询变得陈旧(stale)且需要重新获取时,将从第一个查询开始,按照对应顺序获取每个组。
这样可以确保即使基础数据发生了修改,我们也不会使用过时的游标,也不会得到重复的记录或跳过记录。如果从 queryCache 中删除了无限查询的结果,则分页将在初始状态下重新启动,仅请求初始组(initial group)。
重新获取页面
如果你只想主动重新获取某一部分页面,可以通过传递给refetch(从useInfiniteQuery返回的)一个refetchPage函数来做到
1const { refetch } = useInfiniteQuery("projects", fetchProjects, {2 getNextPageParam: (lastPage, pages) => lastPage.nextCursor,3});4 5// 只重新获取 index===0 的页面6refetch({ refetchPage: (page, index) => index === 0 });你也可以将该方法作为第二个参数(queryFilters)传递给queryClient.refetchQueries, queryClient.invalidateQueries 或者 queryClient.resetQueries 来做到同样的效果
函数签名
refetchPage: (page: TData, index: number, allPages: TData[]) => boolean
该函数对每个页面执行一次,只有返回true的页面才会被重新获取。
如果我需要将自定义信息传递给查询功能怎么办?
默认情况下,从getNextPageParam返回的变量将提供给查询函数,但是在某些情况下,您可能希望重写它。
您可以将自定义变量传递给fetchNextPage函数,该函数将覆盖默认变量,如下所示:
1function Projects() {2 const fetchProjects = ({ pageParam = 0 }) =>3 fetch("/api/projects?cursor=" + pageParam);4 5 const {6 status,7 data,8 isFetching,9 isFetchingNextPage,10 fetchNextPage,11 hasNextPage,12 } = useInfiniteQuery("projects", fetchProjects, {13 getNextPageParam: (lastPage, pages) => lastPage.nextCursor,14 });15 16 // 传递您自己的页面参数17 const skipToCursor50 = () => fetchNextPage({ pageParam: 50 });18}如果我想实现双向无限列表怎么办?
双向列表可以通过使用getPreviousPageParam,fetchPreviousPage,hasPreviousPage和isFetchingPreviousPage属性和函数来实现。
1useInfiniteQuery("projects", fetchProjects, {2 getNextPageParam: (lastPage, pages) => lastPage.nextCursor,3 getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,4});如果我想以相反的顺序显示页面呢?
有时您可能想以相反的顺序显示页面。 在这种情况下,可以使用select选项:
1useInfiniteQuery("projects", fetchProjects, {2 select: (data) => ({3 pages: [...data.pages].reverse(),4 pageParams: [...data.pageParams].reverse(),5 }),6});如果要手动更新无限查询该怎么办
手动删除第一页:
1queryClient.setQueryData("projects", (data) => ({2 pages: data.pages.slice(1),3 pageParams: data.pageParams.slice(1),4}));从单独页面中手动删除单个值:
1const newPagesArray =2 oldPagesArray?.pages.map((page) =>3 page.filter((val) => val.id !== updatedId),4 ) ?? [];5 6queryClient.setQueryData("projects", (data) => ({7 pages: newPagesArray,8 pageParams: data.pageParams,9}));确保分页页面和pageParams保持相同的数据结构!