译零 TransZero · TanStack Query v4 中文文档
主站

无限查询 Infinite Queries

通过"加载更多"来附加更多的数据到现有数据集,或者是通过"无限滚动"来呈现列表,这些都是非常常见的 UI 模式。 正巧,Vue Query 支持一个有用的useQuery版本,称为useInfiniteQuery,很适合用来查询这些类型的数据。

使用useInfiniteQuery时,需要注意一些不同之处:

  • data现在是一个包含无限查询数据的对象
    • data.pages数组包含已获取的分页页面
    • data.pageParams数组包含用于获取分页页面的参数
  • 现在可以使用fetchNextPage和fetchPreviousPage函数
  • getNextPageParam和getPreviousPageParam选项可用于确定是否有更多数据要加载和或用来获取它所需的信息。此信息作为查询函数中的附加参数提供(在调用fetchNextPage或fetchPreviousPage函数时可以有选择地覆盖此信息)
  • hasNextPage布尔值现在可用。如果getNextPageParam返回的值不是undefined的,则为true
  • hasPreviousPage布尔值现在可用。如果getPreviousPageParam返回的值不是undefined的,则为true
  • isFetchingNextPage和isFetchingPreviousPage布尔值来区分后台刷新状态和加载更多状态

注意:在查询中使用initialData或select参数时,请确保在重组(restructure)数据时仍包含data.pages和data.pageParams属性,否则查询返回时将覆盖你的更改!

例子

假设我们有一个基于游标索引的,且每次返回三项projects的 API,以及一个可用于获取下一组项目的游标:

TypeScript
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函数。

HTML
1<script setup>2  import { useInfiniteQuery } from "@tanstack/vue-query";34  const fetchProjects = async ({ pageParam = 0 }) => {5    const res = await fetch("/api/projects?cursor=" + pageParam);6    return res.json();7  };89  const {10    data,11    error,12    fetchNextPage,13    hasNextPage,14    isFetching,15    isFetchingNextPage,16    isLoading,17    isError,18  } = useInfiniteQuery({19    queryKey: ["projects"],20    queryFn: fetchProjects,21    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,22  });23</script>24 25<template>26  <span v-if="isLoading">Loading...</span>27  <span v-else-if="isError">Error: {{ error.message }}</span>28  <div v-else-if="data">29    <span v-if="isFetching && !isFetchingNextPage">Fetching...</span>30    <ul v-for="(group, index) in data.pages" :key="index">31      <li v-for="project in group.projects" :key="project.id">32        {{ project.name }}33      </li>34    </ul>35    <button36      @click="() => fetchNextPage()"37      :disabled="!hasNextPage || isFetchingNextPage"38    >39      <span v-if="isFetchingNextPage">Loading more...</span>40      <span v-else-if="hasNextPage">Load More</span>41      <span v-else>Nothing more to load</span>42    </button>43  </div>44</template>

当无限查询需要重新获取时会发生什么

当无限查询变得陈旧(stale)且需要重新获取时,将从第一个查询开始,按照对应顺序获取每个组。 这样可以确保即使基础数据发生了修改,我们也不会使用过时的游标,也不会得到重复的记录或跳过记录。 如果从 queryCache 中删除了无限查询的结果,则分页将在初始状态下重新启动,仅请求初始的第一个分组(initial group)。

重新获取页面

如果你只想主动重新获取某一部分页面,可以通过传递给refetch(从useInfiniteQuery返回的)一个refetchPage函数来做到

TypeScript
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函数,该函数将覆盖默认变量,如下所示:

TypeScript
1const fetchProjects = ({ pageParam = 0 }) =>2  fetch("/api/projects?cursor=" + pageParam);3 4const { fetchNextPage } = useInfiniteQuery({5  queryKey: ["projects"],6  queryFn: fetchProjects,7  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,8});9 10// Pass your own page param11const skipToCursor50 = () => fetchNextPage({ pageParam: 50 });

如果我想实现双向无限列表怎么办

双向列表可以通过使用getPreviousPageParam,fetchPreviousPage,hasPreviousPage和isFetchingPreviousPage属性和函数来实现。

TypeScript
1useInfiniteQuery(["projects"], fetchProjects, {2  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,3  getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,4});

如果我想以相反的顺序显示页面呢

有时你可能想以相反的顺序显示页面。在这种情况下,可以使用select选项:

TypeScript
1useInfiniteQuery(["projects"], fetchProjects, {2  select: (data) => ({3    pages: [...data.pages].reverse(),4    pageParams: [...data.pageParams].reverse(),5  }),6});

如果要手动更新无限查询该怎么办

手动删除第一页:

TypeScript
1queryClient.setQueryData(["projects"], (data) => ({2  pages: data.pages.slice(1),3  pageParams: data.pageParams.slice(1),4}));

从单个页面中手动删除某一个值:

TypeScript
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保持相同的数据结构!

本页为 TanStack Query 文档(Vue 版)的非官方中文翻译,原文由上游团队维护,遵循 MIT 许可。 译文如有偏差,以英文原文为准。

本页译文取自社区中文版 react-query-web-i18n(MIT),非本站翻译,已停止更新,内容可能与当前 API 有出入。

译自上游 (未记录版本)